import contextlib
import functools
import importlib
import inspect
import itertools
import logging
import math
import re
import threading
import types
from fractions import Fraction
from typing import (
    AbstractSet,
    Any,
    Callable,
    Dict,
    Iterable,
    Mapping,
    Optional,
    Tuple,
    Union,
)

import basilisp.lang.keyword as kw
import basilisp.lang.list as llist
import basilisp.lang.map as lmap
import basilisp.lang.obj as lobj
import basilisp.lang.seq as lseq
import basilisp.lang.set as lset
import basilisp.lang.symbol as sym
import basilisp.lang.vector as vec
from basilisp.lang.atom import Atom
from basilisp.lang.interfaces import (
    IAssociative,
    IBlockingDeref,
    IDeref,
    IPersistentCollection,
    IPersistentList,
    IPersistentMap,
    IPersistentSet,
    IPersistentVector,
    ISeq,
    ISeqable,
)
from basilisp.lang.typing import LispNumber
from basilisp.lang.util import munge
from basilisp.logconfig import TRACE
from basilisp.util import Maybe

logger = logging.getLogger(__name__)

# Public constants
CORE_NS = "basilisp.core"
NS_VAR_NAME = "*ns*"
NS_VAR_NS = CORE_NS
REPL_DEFAULT_NS = "user"

# Private string constants
_GENERATED_PYTHON_VAR_NAME = "*generated-python*"
_PRINT_GENERATED_PY_VAR_NAME = "*print-generated-python*"
_PRINT_DUP_VAR_NAME = "*print-dup*"
_PRINT_LENGTH_VAR_NAME = "*print-length*"
_PRINT_LEVEL_VAR_NAME = "*print-level*"
_PRINT_META_VAR_NAME = "*print-meta*"
_PRINT_READABLY_VAR_NAME = "*print-readably*"

# Common meta keys
_DYNAMIC_META_KEY = kw.keyword("dynamic")
_PRIVATE_META_KEY = kw.keyword("private")
_REDEF_META_KEY = kw.keyword("redef")

# Special form values, used for resolving Vars
_AWAIT = sym.symbol("await")
_CATCH = sym.symbol("catch")
_DEF = sym.symbol("def")
_DEFTYPE = sym.symbol("deftype*")
_DO = sym.symbol("do")
_FINALLY = sym.symbol("finally")
_FN = sym.symbol("fn*")
_IF = sym.symbol("if")
_IMPORT = sym.symbol("import*")
_INTEROP_CALL = sym.symbol(".")
_INTEROP_PROP = sym.symbol(".-")
_LET = sym.symbol("let*")
_LOOP = sym.symbol("loop*")
_QUOTE = sym.symbol("quote")
_RECUR = sym.symbol("recur")
_SET_BANG = sym.symbol("set!")
_THROW = sym.symbol("throw")
_TRY = sym.symbol("try")
_VAR = sym.symbol("var")
_SPECIAL_FORMS = lset.s(
    _AWAIT,
    _CATCH,
    _DEF,
    _DEFTYPE,
    _DO,
    _FINALLY,
    _FN,
    _IF,
    _IMPORT,
    _INTEROP_CALL,
    _INTEROP_PROP,
    _LET,
    _LOOP,
    _QUOTE,
    _RECUR,
    _SET_BANG,
    _THROW,
    _TRY,
    _VAR,
)

CompletionMatcher = Callable[[Tuple[sym.Symbol, Any]], bool]
CompletionTrimmer = Callable[[Tuple[sym.Symbol, Any]], str]

def _new_module(name: str, doc=None) -> types.ModuleType:
    """Create a new empty Basilisp Python module.
    Modules are created for each Namespace when it is created."""
    mod = types.ModuleType(name, doc=doc)
    mod.__loader__ = None
    mod.__package__ = None
    mod.__spec__ = None
    mod.__basilisp_bootstrapped__ = False  # type: ignore
    return mod

class RuntimeException(Exception):
    pass

class Var:
    __slots__ = ("_name", "_ns", "_root", "_dynamic", "_is_bound", "_tl", "_meta")

    def __init__(
        self, ns: "Namespace", name: sym.Symbol, dynamic: bool = False, meta=None
    ) -> None:
        self._ns = ns
        self._name = name
        self._root = None
        self._dynamic = dynamic
        self._is_bound = False
        self._tl = threading.local()
        self._meta = meta

        if dynamic:
            self._tl.bindings = []

            # If this var was created with the dynamic keyword argument, then the
            # Var metadata should also specify that the Var is dynamic.
            if isinstance(self._meta, lmap.Map):
                if not self._meta.entry(_DYNAMIC_META_KEY):
                    self._meta = self._meta.assoc(_DYNAMIC_META_KEY, True)
            else:
                self._meta = lmap.map({_DYNAMIC_META_KEY: True})

    def __repr__(self):
        return f"#'{self.ns.name}/{self.name}"

    @property
    def meta(self):
        return self._meta

    @meta.setter
    def meta(self, meta):
        self._meta = meta

    @property
    def ns(self) -> "Namespace":
        return self._ns

    @property
    def name(self) -> sym.Symbol:
        return self._name

    @property
    def dynamic(self) -> bool:
        return self._dynamic

    @dynamic.setter
    def dynamic(self, is_dynamic: bool):
        self._dynamic = is_dynamic

    @property
    def is_private(self) -> Optional[bool]:
        try:
            return self.meta.entry(_PRIVATE_META_KEY)
        except AttributeError:
            return False

    @property
    def is_bound(self) -> bool:
        return self._is_bound

    @property
    def root(self):
        return self._root

    @root.setter
    def root(self, val):
        self._is_bound = True
        self._root = val

    def push_bindings(self, val):
        if not hasattr(self._tl, "bindings"):
            self._tl.bindings = []
        self._tl.bindings.append(val)

    def pop_bindings(self):
        return self._tl.bindings.pop()

    @property
    def value(self):
        if self._dynamic:
            assert hasattr(self._tl, "bindings")
            if len(self._tl.bindings) > 0:
                return self._tl.bindings[-1]
        return self._root

    @value.setter
    def value(self, v):
        if self._dynamic:
            assert hasattr(self._tl, "bindings")
            if len(self._tl.bindings) > 0:
                self._tl.bindings[-1] = v
            else:
                self.push_bindings(v)
            return
        self._root = v

    @staticmethod
    def intern(
        ns: sym.Symbol, name: sym.Symbol, val, dynamic: bool = False, meta=None
    ) -> "Var":
        """Intern the value bound to the symbol `name` in namespace `ns`."""
        var_ns = Namespace.get_or_create(ns)
        var = var_ns.intern(name, Var(var_ns, name, dynamic=dynamic, meta=meta))
        var.root = val
        return var

    @staticmethod
    def intern_unbound(
        ns: sym.Symbol, name: sym.Symbol, dynamic: bool = False, meta=None
    ) -> "Var":
        """Create a new unbound `Var` instance to the symbol `name` in namespace `ns`."""
        var_ns = Namespace.get_or_create(ns)
        return var_ns.intern(name, Var(var_ns, name, dynamic=dynamic, meta=meta))

    @staticmethod
    def find_in_ns(ns_sym: sym.Symbol, name_sym: sym.Symbol) -> "Optional[Var]":
        """Return the value current bound to the name `name_sym` in the namespace
        specified by `ns_sym`."""
        ns = Namespace.get(ns_sym)
        if ns:
            return ns.find(name_sym)
        return None

    @staticmethod
    def find(ns_qualified_sym: sym.Symbol) -> "Optional[Var]":
        """Return the value currently bound to the name in the namespace specified
        by `ns_qualified_sym`."""
        ns = Maybe(ns_qualified_sym.ns).or_else_raise(
            lambda: ValueError(
                f"Namespace must be specified in Symbol {ns_qualified_sym}"
            )
        )
        ns_sym = sym.symbol(ns)
        name_sym = sym.symbol(ns_qualified_sym.name)
        return Var.find_in_ns(ns_sym, name_sym)

    @staticmethod
    def find_safe(ns_qualified_sym: sym.Symbol) -> "Var":
        """Return the Var currently bound to the name in the namespace specified
        by `ns_qualified_sym`. If no Var is bound to that name, raise an exception.

        This is a utility method to return useful debugging information when code
        refers to an invalid symbol at runtime."""
        v = Var.find(ns_qualified_sym)
        if v is None:
            raise RuntimeException(
                f"Unable to resolve symbol {ns_qualified_sym} in this context"
            )
        return v

AliasMap = lmap.Map[sym.Symbol, sym.Symbol]
ModuleMap = lmap.Map[sym.Symbol, types.ModuleType]
NamespaceMap = lmap.Map[sym.Symbol, "Namespace"]
VarMap = lmap.Map[sym.Symbol, Var]

class Namespace:
    """Namespaces serve as organizational units in Basilisp code, just as
    they do in Clojure code. Vars are mutable containers for functions and
    data which may be interned in a namespace and referred to by a Symbol.
    Namespaces additionally may have aliases to other namespaces, so code
    organized in one namespace may conveniently refer to code or data in
    other namespaces using that alias as the Symbol's namespace.
    Namespaces are constructed def-by-def as Basilisp reads in each form
    in a file (which will typically declare a namespace at the top).
    Namespaces have the following fields of interest:
    - `aliases` is a mapping between a symbolic alias and another
      Namespace. The fully qualified name of a namespace is also
      an alias for itself.

    - `imports` is a mapping of names to Python modules imported
      into the current namespace.

    - `interns` is a mapping between a symbolic name and a Var. The
      Var may point to code, data, or nothing, if it is unbound. Vars
      in `interns` are interned in _this_ namespace.

    - `refers` is a mapping between a symbolic name and a Var. Vars in
      `refers` are interned in another namespace and are only referred
      to without an alias in this namespace.
    """

    DEFAULT_IMPORTS = Atom(
        lset.set(
            map(
                sym.symbol,
                [
                    "attr",
                    "builtins",
                    "io",
                    "operator",
                    "sys",
                    "basilisp.lang.atom",
                    "basilisp.lang.compiler",
                    "basilisp.lang.delay",
                    "basilisp.lang.exception",
                    "basilisp.lang.interfaces",
                    "basilisp.lang.keyword",
                    "basilisp.lang.list",
                    "basilisp.lang.map",
                    "basilisp.lang.multifn",
                    "basilisp.lang.reader",
                    "basilisp.lang.runtime",
                    "basilisp.lang.seq",
                    "basilisp.lang.set",
                    "basilisp.lang.symbol",
                    "basilisp.lang.vector",
                    "basilisp.lang.util",
                ],
            )
        )
    )
    GATED_IMPORTS = lset.set(["basilisp.core"])

    _NAMESPACES: Atom[NamespaceMap] = Atom(lmap.Map.empty())

    __slots__ = (
        "_name",
        "_module",
        "_interns",
        "_refers",
        "_aliases",
        "_imports",
        "_import_aliases",
    )

    def __init__(self, name: sym.Symbol, module: types.ModuleType = None) -> None:
        self._name = name
        self._module = Maybe(module).or_else(lambda: _new_module(name.as_python_sym()))

        self._aliases: Atom[NamespaceMap] = Atom(lmap.Map.empty())
        self._imports: Atom[ModuleMap] = Atom(
            lmap.map(
                dict(
                    map(
                        lambda s: (s, importlib.import_module(s.name)),
                        Namespace.DEFAULT_IMPORTS.deref(),
                    )
                )
            )
        )
        self._import_aliases: Atom[AliasMap] = Atom(lmap.Map.empty())
        self._interns: Atom[VarMap] = Atom(lmap.Map.empty())
        self._refers: Atom[VarMap] = Atom(lmap.Map.empty())

    @classmethod
    def add_default_import(cls, module: str):
        """Add a gated default import to the default imports.

        In particular, we need to avoid importing 'basilisp.core' before we have
        finished macro-expanding."""
        if module in cls.GATED_IMPORTS:
            cls.DEFAULT_IMPORTS.swap(lambda s: s.cons(sym.symbol(module)))

    @property
    def name(self) -> str:
        return self._name.name

    @property
    def module(self) -> types.ModuleType:
        return self._module

    @module.setter
    def module(self, m: types.ModuleType):
        """Override the Python module for this Namespace.

        ***WARNING**
        This should only be done by basilisp.importer code to make sure the
        correct module is generated for `basilisp.core`."""
        self._module = m

    @property
    def aliases(self) -> NamespaceMap:
        """A mapping between a symbolic alias and another Namespace. The
        fully qualified name of a namespace is also an alias for itself."""
        return self._aliases.deref()

    @property
    def imports(self) -> ModuleMap:
        """A mapping of names to Python modules imported into the current
        namespace."""
        return self._imports.deref()

    @property
    def import_aliases(self) -> AliasMap:
        """A mapping of a symbolic alias and a Python module name."""
        return self._import_aliases.deref()

    @property
    def interns(self) -> VarMap:
        """A mapping between a symbolic name and a Var. The Var may point to
        code, data, or nothing, if it is unbound. Vars in `interns` are
        interned in _this_ namespace."""
        return self._interns.deref()

    @property
    def refers(self) -> VarMap:
        """A mapping between a symbolic name and a Var. Vars in refers are
        interned in another namespace and are only referred to without an
        alias in this namespace."""
        return self._refers.deref()

    def __repr__(self):
        return f"{self._name}"

    def __hash__(self):
        return hash(self._name)

    def add_alias(self, alias: sym.Symbol, namespace: "Namespace") -> None:
        """Add a Symbol alias for the given Namespace."""
        self._aliases.swap(lambda m: m.assoc(alias, namespace))

    def get_alias(self, alias: sym.Symbol) -> "Optional[Namespace]":
        """Get the Namespace aliased by Symbol or None if it does not exist."""
        return self.aliases.entry(alias, None)

    def intern(self, sym: sym.Symbol, var: Var, force: bool = False) -> Var:
        """Intern the Var given in this namespace mapped by the given Symbol.
        If the Symbol already maps to a Var, this method _will not overwrite_
        the existing Var mapping unless the force keyword argument is given
        and is True."""
        m: lmap.Map = self._interns.swap(Namespace._intern, sym, var, force=force)
        return m.entry(sym)

    @staticmethod
    def _intern(
        m: lmap.Map, sym: sym.Symbol, new_var: Var, force: bool = False
    ) -> lmap.Map:
        """Swap function used by intern to atomically intern a new variable in
        the symbol mapping for this Namespace."""
        var = m.entry(sym, None)
        if var is None or force:
            return m.assoc(sym, new_var)
        return m

    def find(self, sym: sym.Symbol) -> Optional[Var]:
        """Find Vars mapped by the given Symbol input or None if no Vars are
        mapped by that Symbol."""
        v = self.interns.entry(sym, None)
        if v is None:
            return self.refers.entry(sym, None)
        return v

    def add_import(
        self, sym: sym.Symbol, module: types.ModuleType, *aliases: sym.Symbol
    ) -> None:
        """Add the Symbol as an imported Symbol in this Namespace. If aliases are given,
        the aliases will be applied to the """
        self._imports.swap(lambda m: m.assoc(sym, module))
        if aliases:
            self._import_aliases.swap(
                lambda m: m.assoc(
                    *itertools.chain.from_iterable([(alias, sym) for alias in aliases])
                )
            )

    def get_import(self, sym: sym.Symbol) -> Optional[types.ModuleType]:
        """Return the module if a moduled named by sym has been imported into
        this Namespace, None otherwise.

        First try to resolve a module directly with the given name. If no module
        can be resolved, attempt to resolve the module using import aliases."""
        mod = self.imports.entry(sym, None)
        if mod is None:
            alias = self.import_aliases.get(sym, None)
            if alias is None:
                return None
            return self.imports.entry(alias, None)
        return mod

    def add_refer(self, sym: sym.Symbol, var: Var) -> None:
        """Refer var in this namespace under the name sym."""
        if not var.is_private:
            self._refers.swap(lambda s: s.assoc(sym, var))

    def get_refer(self, sym: sym.Symbol) -> Optional[Var]:
        """Get the Var referred by Symbol or None if it does not exist."""
        return self.refers.entry(sym, None)

    @classmethod
    def __refer_all(cls, refers: lmap.Map, other_ns_interns: lmap.Map) -> lmap.Map:
        """Refer all _public_ interns from another namespace."""
        final_refers = refers
        for entry in other_ns_interns:
            s: sym.Symbol = entry.key
            var: Var = entry.value
            if not var.is_private:
                final_refers = final_refers.assoc(s, var)
        return final_refers

    def refer_all(self, other_ns: "Namespace"):
        """Refer all the Vars in the other namespace."""
        self._refers.swap(Namespace.__refer_all, other_ns.interns)

    @classmethod
    def ns_cache(cls) -> lmap.Map:
        """Return a snapshot of the Namespace cache."""
        return cls._NAMESPACES.deref()

    @staticmethod
    def __get_or_create(
        ns_cache: NamespaceMap,
        name: sym.Symbol,
        module: types.ModuleType = None,
        core_ns_name=CORE_NS,
    ) -> lmap.Map:
        """Private swap function used by `get_or_create` to atomically swap
        the new namespace map into the global cache."""
        ns = ns_cache.entry(name, None)
        if ns is not None:
            return ns_cache
        new_ns = Namespace(name, module=module)
        if name.name != core_ns_name:
            core_ns = ns_cache.entry(sym.symbol(core_ns_name), None)
            assert core_ns is not None, "Core namespace not loaded yet!"
            new_ns.refer_all(core_ns)
        return ns_cache.assoc(name, new_ns)

    @classmethod
    def get_or_create(
        cls, name: sym.Symbol, module: types.ModuleType = None
    ) -> "Namespace":
        """Get the namespace bound to the symbol `name` in the global namespace
        cache, creating it if it does not exist.
        Return the namespace."""
        return cls._NAMESPACES.swap(Namespace.__get_or_create, name, module=module)[
            name
        ]

    @classmethod
    def get(cls, name: sym.Symbol) -> "Optional[Namespace]":
        """Get the namespace bound to the symbol `name` in the global namespace
        cache. Return the namespace if it exists or None otherwise.."""
        return cls._NAMESPACES.deref().entry(name, None)

    @classmethod
    def remove(cls, name: sym.Symbol) -> Optional["Namespace"]:
        """Remove the namespace bound to the symbol `name` in the global
        namespace cache and return that namespace.
        Return None if the namespace did not exist in the cache."""
        while True:
            oldval: lmap.Map = cls._NAMESPACES.deref()
            ns: Optional[Namespace] = oldval.entry(name, None)
            newval = oldval
            if ns is not None:
                newval = oldval.dissoc(name)
            if cls._NAMESPACES.compare_and_set(oldval, newval):
                return ns

    # REPL Completion support

    @staticmethod
    def __completion_matcher(text: str) -> CompletionMatcher:
        """Return a function which matches any symbol keys from map entries
        against the given text."""

        def is_match(entry: Tuple[sym.Symbol, Any]) -> bool:
            return entry[0].name.startswith(text)

        return is_match

    def __complete_alias(
        self, prefix: str, name_in_ns: Optional[str] = None
    ) -> Iterable[str]:
        """Return an iterable of possible completions matching the given
        prefix from the list of aliased namespaces. If name_in_ns is given,
        further attempt to refine the list to matching names in that namespace."""
        candidates = filter(
            Namespace.__completion_matcher(prefix), [(s, n) for s, n in self.aliases]
        )
        if name_in_ns is not None:
            for _, candidate_ns in candidates:
                for match in candidate_ns.__complete_interns(
                    name_in_ns, include_private_vars=False
                ):
                    yield f"{prefix}/{match}"
        else:
            for alias, _ in candidates:
                yield f"{alias}/"

    def __complete_imports_and_aliases(
        self, prefix: str, name_in_module: Optional[str] = None
    ) -> Iterable[str]:
        """Return an iterable of possible completions matching the given
        prefix from the list of imports and aliased imports. If name_in_module
        is given, further attempt to refine the list to matching names in that
        namespace."""
        imports = self.imports
        aliases = lmap.map(
            {
                alias: imports.entry(import_name)
                for alias, import_name in self.import_aliases
            }
        )

        candidates = filter(
            Namespace.__completion_matcher(prefix), itertools.chain(aliases, imports)
        )
        if name_in_module is not None:
            for _, module in candidates:
                for name in module.__dict__:
                    if name.startswith(name_in_module):
                        yield f"{prefix}/{name}"
        else:
            for candidate_name, _ in candidates:
                yield f"{candidate_name}/"

    def __complete_interns(
        self, value: str, include_private_vars: bool = True
    ) -> Iterable[str]:
        """Return an iterable of possible completions matching the given
        prefix from the list of interned Vars."""
        if include_private_vars:
            is_match = Namespace.__completion_matcher(value)
        else:
            _is_match = Namespace.__completion_matcher(value)

            def is_match(entry: Tuple[sym.Symbol, Var]) -> bool:
                return _is_match(entry) and not entry[1].is_private

        return map(
            lambda entry: f"{entry[0].name}",
            filter(is_match, [(s, v) for s, v in self.interns]),
        )

    def __complete_refers(self, value: str) -> Iterable[str]:
        """Return an iterable of possible completions matching the given
        prefix from the list of referred Vars."""
        return map(
            lambda entry: f"{entry[0].name}",
            filter(
                Namespace.__completion_matcher(value), [(s, v) for s, v in self.refers]
            ),
        )

    def complete(self, text: str) -> Iterable[str]:
        """Return an iterable of possible completions for the given text in
        this namespace."""
        assert not text.startswith(":")

        if "/" in text:
            prefix, suffix = text.split("/", maxsplit=1)
            results = itertools.chain(
                self.__complete_alias(prefix, name_in_ns=suffix),
                self.__complete_imports_and_aliases(prefix, name_in_module=suffix),
            )
        else:
            results = itertools.chain(
                self.__complete_alias(text),
                self.__complete_imports_and_aliases(text),
                self.__complete_interns(text),
                self.__complete_refers(text),
            )

        return results

###################
# Runtime Support #
###################

def first(o):
    """If o is a ISeq, return the first element from o. If o is None, return
    None. Otherwise, coerces o to a Seq and returns the first."""
    if o is None:
        return None
    if isinstance(o, ISeq):
        return o.first
    s = to_seq(o)
    if s is None:
        return None
    return s.first

def rest(o) -> Optional[ISeq]:
    """If o is a ISeq, return the elements after the first in o. If o is None,
    returns an empty seq. Otherwise, coerces o to a seq and returns the rest."""
    if o is None:
        return None
    if isinstance(o, ISeq):
        s = o.rest
        if s is None:
            return lseq.EMPTY
        return s
    n = to_seq(o)
    if n is None:
        return lseq.EMPTY
    return n.rest

def nthrest(coll, i: int):
    """Returns the nth rest sequence of coll, or coll if i is 0."""
    while True:
        if coll is None:
            return None
        if i == 0:
            return coll
        i -= 1
        coll = rest(coll)

def next_(o) -> Optional[ISeq]:
    """Calls rest on o. If o returns an empty sequence or None, returns None.
    Otherwise, returns the elements after the first in o."""
    s = rest(o)
    if not s:
        return None
    return s

def nthnext(coll, i: int) -> Optional[ISeq]:
    """Returns the nth next sequence of coll."""
    while True:
        if coll is None:
            return None
        if i == 0:
            return to_seq(coll)
        i -= 1
        coll = next_(coll)

def cons(o, seq) -> ISeq:
    """Creates a new sequence where o is the first element and seq is the rest.
    If seq is None, return a list containing o. If seq is not a ISeq, attempt
    to coerce it to a ISeq and then cons o onto the resulting sequence."""
    if seq is None:
        return llist.l(o)
    if isinstance(seq, ISeq):
        return seq.cons(o)
    return Maybe(to_seq(seq)).map(lambda s: s.cons(o)).or_else(lambda: llist.l(o))

def _seq_or_nil(s: ISeq) -> Optional[ISeq]:
    """Return None if a ISeq is empty, the ISeq otherwise."""
    if s.is_empty:
        return None
    return s

def to_seq(o) -> Optional[ISeq]:
    """Coerce the argument o to a ISeq. If o is None, return None."""
    if o is None:
        return None
    if isinstance(o, ISeq):
        return _seq_or_nil(o)
    if isinstance(o, ISeqable):
        return _seq_or_nil(o.seq())
    return _seq_or_nil(lseq.sequence(o))

def concat(*seqs) -> ISeq:
    """Concatenate the sequences given by seqs into a single ISeq."""
    allseqs = lseq.sequence(itertools.chain(*filter(None, map(to_seq, seqs))))
    if allseqs is None:
        return lseq.EMPTY
    return allseqs

def apply(f, args):
    """Apply function f to the arguments provided.
    The last argument must always be coercible to a Seq. Intermediate
    arguments are not modified.
    For example:
        (apply max [1 2 3])   ;=> 3
        (apply max 4 [1 2 3]) ;=> 4"""
    final = list(args[:-1])

    try:
        last = args[-1]
    except TypeError as e:
        logger.debug("Ignored %s: %s", type(e).__name__, e)

    s = to_seq(last)
    if s is not None:
        final.extend(s)

    return f(*final)

def apply_kw(f, args):
    """Apply function f to the arguments provided.
    The last argument must always be coercible to a Mapping. Intermediate
    arguments are not modified.
    For example:
        (apply builtins/dict {:a 1} {:b 2})   ;=> #py {:a 1 :b 2}
        (apply builtins/dict {:a 1} {:a 2})   ;=> #py {:a 2}"""
    final = list(args[:-1])

    try:
        last = args[-1]
    except TypeError as e:
        logger.debug("Ignored %s: %s", type(e).__name__, e)

    kwargs = to_py(last, lambda kw: munge(kw.name, allow_builtins=True))
    return f(*final, **kwargs)

__nth_sentinel = object()

def nth(coll, i, notfound=__nth_sentinel):
    """Returns the ith element of coll (0-indexed), if it exists.
    None otherwise. If i is out of bounds, throws an IndexError unless
    notfound is specified."""
    if coll is None:
        return None

    try:
        return coll[i]
    except IndexError as ex:
        if notfound is not __nth_sentinel:
            return notfound
        raise ex
    except TypeError as ex:
        # Log these at TRACE so they don't gum up the DEBUG logs since most
        # cases where this exception occurs are not bugs.
        logger.log(TRACE, "Ignored %s: %s", type(ex).__name__, ex)

    try:
        for j, e in enumerate(coll):
            if i == j:
                return e
        if notfound is not __nth_sentinel:
            return notfound
        raise IndexError(f"Index {i} out of bounds")
    except TypeError:
        pass

    raise TypeError(f"nth not supported on object of type {type(coll)}")

def assoc(m, *kvs):
    """Associate keys to values in associative data structure m. If m is None,
    returns a new Map with key-values kvs."""
    if m is None:
        return lmap.Map.empty().assoc(*kvs)
    if isinstance(m, IAssociative):
        return m.assoc(*kvs)
    raise TypeError(
        f"Object of type {type(m)} does not implement Associative interface"
    )

def update(m, k, f, *args):
    """Updates the value for key k in associative data structure m with the return value from
    calling f(old_v, *args). If m is None, use an empty map. If k is not in m, old_v will be
    None."""
    if m is None:
        return lmap.Map.empty().assoc(k, f(None, *args))
    if isinstance(m, IAssociative):
        old_v = m.entry(k)
        new_v = f(old_v, *args)
        return m.assoc(k, new_v)
    raise TypeError(
        f"Object of type {type(m)} does not implement Associative interface"
    )

def conj(coll, *xs):
    """Conjoin xs to collection. New elements may be added in different positions
    depending on the type of coll. conj returns the same type as coll. If coll
    is None, return a list with xs conjoined."""
    if coll is None:
        l = llist.List.empty()
        return l.cons(*xs)
    if isinstance(coll, IPersistentCollection):
        return coll.cons(*xs)
    raise TypeError(
        f"Object of type {type(coll)} does not implement Collection interface"
    )

def partial(f, *args):
    """Return a function which is the partial application of f with args."""

    @functools.wraps(f)
    def partial_f(*inner_args):
        return f(*itertools.chain(args, inner_args))

    return partial_f

def deref(o, timeout_s=None, timeout_val=None):
    """Dereference a Deref object and return its contents.

    If o is an object implementing IBlockingDeref and timeout_s and
    timeout_val are supplied, deref will wait at most timeout_s seconds,
    returning timeout_val if timeout_s seconds elapse and o has not
    returned."""
    if isinstance(o, IDeref):
        return o.deref()
    elif isinstance(o, IBlockingDeref):
        return o.deref(timeout_s, timeout_val)
    raise TypeError(f"Object of type {type(o)} cannot be dereferenced")

def swap(a: Atom, f, *args):
    """Atomically swap the value of an atom to the return value of (apply f
    current-value args). The function f may be called multiple times while
    swapping, so should be free of side effects. Return the new value."""
    return a.swap(f, *args)

def equals(v1, v2) -> bool:
    """Compare two objects by value. Unlike the standard Python equality operator,
    this function does not consider 1 == True or 0 == False. All other equality
    operations are the same and performed using Python's equality operator."""
    if isinstance(v1, (bool, type(None))) or isinstance(v2, (bool, type(None))):
        return v1 is v2
    return v1 == v2

def divide(x: LispNumber, y: LispNumber) -> LispNumber:
    """Division reducer. If both arguments are integers, return a Fraction.
    Otherwise, return the true division of x and y."""
    if isinstance(x, int) and isinstance(y, int):
        return Fraction(x, y)
    return x / y

def quotient(num, div) -> LispNumber:
    """Return the integral quotient resulting from the division of num by div."""
    return math.trunc(num / div)

def sort(coll, f=None) -> Optional[ISeq]:
    """Return a sorted sequence of the elements in coll. If a comparator
    function f is provided, compare elements in coll using f."""
    return to_seq(sorted(coll, key=Maybe(f).map(functools.cmp_to_key).value))

def contains(coll, k):
    """Return true if o contains the key k."""
    if isinstance(coll, IAssociative):
        return coll.contains(k)
    return k in coll

def get(m, k, default=None):
    """Return the value of k in m. Return default if k not found in m."""
    if isinstance(m, IAssociative):
        return m.entry(k, default=default)

    try:
        return m[k]
    except (KeyError, IndexError, TypeError) as e:
        logger.debug("Ignored %s: %s", type(e).__name__, e)
        return default

def is_special_form(s: sym.Symbol) -> bool:
    """Return True if s names a special form."""
    return s in _SPECIAL_FORMS

@functools.singledispatch
def to_lisp(o, keywordize_keys: bool = True):
    """Recursively convert Python collections into Lisp collections."""
    if not isinstance(o, (dict, frozenset, list, set, tuple)):
        return o
    else:  # pragma: no cover
        return _to_lisp_backup(o, keywordize_keys=keywordize_keys)

def _to_lisp_backup(o, keywordize_keys: bool = True):  # pragma: no cover
    if isinstance(o, Mapping):
        return _to_lisp_map(o, keywordize_keys=keywordize_keys)
    elif isinstance(o, AbstractSet):
        return _to_lisp_set(o, keywordize_keys=keywordize_keys)
    elif isinstance(o, Iterable):
        return _to_lisp_vec(o, keywordize_keys=keywordize_keys)
    else:
        return o

@to_lisp.register(list)
@to_lisp.register(tuple)
def _to_lisp_vec(o: Iterable, keywordize_keys: bool = True) -> vec.Vector:
    return vec.vector(
        map(functools.partial(to_lisp, keywordize_keys=keywordize_keys), o)
    )

@to_lisp.register(dict)
def _to_lisp_map(o: Mapping, keywordize_keys: bool = True) -> lmap.Map:
    kvs = {}
    for k, v in o.items():
        if isinstance(k, str) and keywordize_keys:
            processed_key = kw.keyword(k)
        else:
            processed_key = to_lisp(k, keywordize_keys=keywordize_keys)

        kvs[processed_key] = to_lisp(v, keywordize_keys=keywordize_keys)
    return lmap.map(kvs)

@to_lisp.register(frozenset)
@to_lisp.register(set)
def _to_lisp_set(o: AbstractSet, keywordize_keys: bool = True) -> lset.Set:
    return lset.set(map(functools.partial(to_lisp, keywordize_keys=keywordize_keys), o))

def _kw_name(kw: kw.Keyword) -> str:
    return kw.name

@functools.singledispatch
def to_py(o, keyword_fn: Callable[[kw.Keyword], Any] = _kw_name):
    """Recursively convert Lisp collections into Python collections."""
    if isinstance(o, ISeq):
        return _to_py_list(o, keyword_fn=keyword_fn)
    elif not isinstance(
        o, (IPersistentList, IPersistentMap, IPersistentSet, IPersistentVector)
    ):
        return o
    else:  # pragma: no cover
        return _to_py_backup(o, keyword_fn=keyword_fn)

def _to_py_backup(
    o, keyword_fn: Callable[[kw.Keyword], Any] = _kw_name
):  # pragma: no cover
    if isinstance(o, (IPersistentList, IPersistentVector)):
        return _to_py_list(o, keyword_fn=keyword_fn)
    elif isinstance(o, IPersistentMap):
        return _to_py_map(o, keyword_fn=keyword_fn)
    elif isinstance(o, IPersistentSet):
        return _to_py_set(o, keyword_fn=keyword_fn)
    else:
        return o

@to_py.register(kw.Keyword)
def _to_py_kw(o: kw.Keyword, keyword_fn: Callable[[kw.Keyword], Any] = _kw_name) -> Any:
    return keyword_fn(o)

@to_py.register(llist.List)
@to_py.register(ISeq)
@to_py.register(vec.Vector)
def _to_py_list(
    o: Union[IPersistentList, ISeq, IPersistentVector],
    keyword_fn: Callable[[kw.Keyword], Any] = _kw_name,
) -> list:
    return list(map(functools.partial(to_py, keyword_fn=keyword_fn), o))

@to_py.register(lmap.Map)
def _to_py_map(
    o: IPersistentMap, keyword_fn: Callable[[kw.Keyword], Any] = _kw_name
) -> dict:
    return {
        to_py(key, keyword_fn=keyword_fn): to_py(value, keyword_fn=keyword_fn)
        for key, value in o
    }

@to_py.register(lset.Set)
def _to_py_set(
    o: IPersistentSet, keyword_fn: Callable[[kw.Keyword], Any] = _kw_name
) -> set:
    return set(to_py(e, keyword_fn=keyword_fn) for e in o)

def lrepr(o, human_readable: bool = False) -> str:
    """Produce a string representation of an object. If human_readable is False,
    the string representation of Lisp objects is something that can be read back
    in by the reader as the same object."""
    core_ns = Namespace.get(sym.symbol(CORE_NS))
    assert core_ns is not None
    return lobj.lrepr(
        o,
        human_readable=human_readable,
        print_dup=core_ns.find(sym.symbol(_PRINT_DUP_VAR_NAME)).value,  # type: ignore
        print_length=core_ns.find(  # type: ignore
            sym.symbol(_PRINT_LENGTH_VAR_NAME)
        ).value,
        print_level=core_ns.find(  # type: ignore
            sym.symbol(_PRINT_LEVEL_VAR_NAME)
        ).value,
        print_meta=core_ns.find(sym.symbol(_PRINT_META_VAR_NAME)).value,  # type: ignore
        print_readably=core_ns.find(  # type: ignore
            sym.symbol(_PRINT_READABLY_VAR_NAME)
        ).value,
    )

def lstr(o) -> str:
    """Produce a human readable string representation of an object."""
    return lrepr(o, human_readable=True)

__NOT_COMPLETEABLE = re.compile(r"^[0-9].*")

def repl_complete(text: str, state: int) -> Optional[str]:
    """Completer function for Python's readline/libedit implementation."""
    # Can't complete Keywords, Numerals
    if __NOT_COMPLETEABLE.match(text):
        return None
    elif text.startswith(":"):
        completions = kw.complete(text)
    else:
        ns = get_current_ns()
        completions = ns.complete(text)

    return list(completions)[state] if completions is not None else None

####################
# Compiler Support #
####################

def _collect_args(args) -> ISeq:
    """Collect Python starred arguments into a Basilisp list."""
    if isinstance(args, tuple):
        return llist.list(args)
    raise TypeError("Python variadic arguments should always be a tuple")

class _TrampolineArgs:
    __slots__ = ("_has_varargs", "_args", "_kwargs")

    def __init__(self, has_varargs: bool, *args, **kwargs) -> None:
        self._has_varargs = has_varargs
        self._args = args
        self._kwargs = kwargs

    @property
    def args(self) -> Tuple:
        """Return the arguments for a trampolined function. If the function
        that is being trampolined has varargs, unroll the final argument if
        it is a sequence."""
        if not self._has_varargs:
            return self._args

        try:
            final = self._args[-1]
            if isinstance(final, ISeq):
                inits = self._args[:-1]
                return tuple(itertools.chain(inits, final))
            return self._args
        except IndexError:
            return ()

    @property
    def kwargs(self) -> Dict:
        return self._kwargs

def _trampoline(f):
    """Trampoline a function repeatedly until it is finished recurring to help
    avoid stack growth."""

    @functools.wraps(f)
    def trampoline(*args, **kwargs):
        while True:
            ret = f(*args, **kwargs)
            if isinstance(ret, _TrampolineArgs):
                args = ret.args
                kwargs = ret.kwargs
                continue
            return ret

    return trampoline

def _with_attrs(**kwargs):
    """Decorator to set attributes on a function. Returns the original
    function after setting the attributes named by the keyword arguments."""

    def decorator(f):
        for k, v in kwargs.items():
            setattr(f, k, v)
        return f

    return decorator

def _fn_with_meta(f, meta: Optional[lmap.Map]):
    """Return a new function with the given meta. If the function f already
    has a meta map, then merge the """

    if not isinstance(meta, lmap.Map):
        raise TypeError("meta must be a map")

    if inspect.iscoroutinefunction(f):

        @functools.wraps(f)
        async def wrapped_f(*args, **kwargs):
            return await f(*args, **kwargs)

    else:

        @functools.wraps(f)  # type: ignore
        def wrapped_f(*args, **kwargs):
            return f(*args, **kwargs)

    wrapped_f.meta = (  # type: ignore
        f.meta.update(meta)
        if hasattr(f, "meta") and isinstance(f.meta, lmap.Map)
        else meta
    )
    wrapped_f.with_meta = partial(_fn_with_meta, wrapped_f)  # type: ignore
    return wrapped_f

def _basilisp_fn(f):
    """Create a Basilisp function, setting meta and supplying a with_meta
    method implementation."""
    assert not hasattr(f, "meta")
    f._basilisp_fn = True
    f.meta = None
    f.with_meta = partial(_fn_with_meta, f)
    return f

#########################
# Bootstrap the Runtime #
#########################

def init_ns_var(which_ns: str = CORE_NS, ns_var_name: str = NS_VAR_NAME) -> Var:
    """Initialize the dynamic `*ns*` variable in the Namespace `which_ns`."""
    core_sym = sym.Symbol(which_ns)
    core_ns = Namespace.get_or_create(core_sym)
    ns_var = Var.intern(core_sym, sym.Symbol(ns_var_name), core_ns, dynamic=True)
    logger.debug(f"Created namespace variable {sym.symbol(ns_var_name, ns=which_ns)}")
    return ns_var

def set_current_ns(
    ns_name: str,
    module: types.ModuleType = None,
    ns_var_name: str = NS_VAR_NAME,
    ns_var_ns: str = NS_VAR_NS,
) -> Var:
    """Set the value of the dynamic variable `*ns*` in the current thread."""
    symbol = sym.Symbol(ns_name)
    ns = Namespace.get_or_create(symbol, module=module)
    ns_var_sym = sym.Symbol(ns_var_name, ns=ns_var_ns)
    ns_var = Maybe(Var.find(ns_var_sym)).or_else_raise(
        lambda: RuntimeException(
            f"Dynamic Var {sym.Symbol(ns_var_name, ns=ns_var_ns)} not bound!"
        )
    )
    ns_var.push_bindings(ns)
    logger.debug(f"Setting {ns_var_sym} to {ns}")
    return ns_var

@contextlib.contextmanager
def ns_bindings(
    ns_name: str,
    module: types.ModuleType = None,
    ns_var_name: str = NS_VAR_NAME,
    ns_var_ns: str = NS_VAR_NS,
):
    """Context manager for temporarily changing the value of basilisp.core/*ns*."""
    symbol = sym.Symbol(ns_name)
    ns = Namespace.get_or_create(symbol, module=module)
    ns_var_sym = sym.Symbol(ns_var_name, ns=ns_var_ns)
    ns_var = Maybe(Var.find(ns_var_sym)).or_else_raise(
        lambda: RuntimeException(
            f"Dynamic Var {sym.Symbol(ns_var_name, ns=ns_var_ns)} not bound!"
        )
    )

    try:
        logger.debug(f"Binding {ns_var_sym} to {ns}")
        ns_var.push_bindings(ns)
        yield ns_var.value
    finally:
        ns_var.pop_bindings()
        logger.debug(f"Reset bindings for {ns_var_sym} to {ns_var.value}")

@contextlib.contextmanager
def remove_ns_bindings(ns_var_name: str = NS_VAR_NAME, ns_var_ns: str = NS_VAR_NS):
    """Context manager to pop the most recent bindings for basilisp.core/*ns* after
    completion of the code under management."""
    ns_var_sym = sym.Symbol(ns_var_name, ns=ns_var_ns)
    ns_var = Maybe(Var.find(ns_var_sym)).or_else_raise(
        lambda: RuntimeException(
            f"Dynamic Var {sym.Symbol(ns_var_name, ns=ns_var_ns)} not bound!"
        )
    )
    try:
        yield
    finally:
        ns_var.pop_bindings()
        logger.debug(f"Reset bindings for {ns_var_sym} to {ns_var.value}")

def get_current_ns(
    ns_var_name: str = NS_VAR_NAME, ns_var_ns: str = NS_VAR_NS
) -> Namespace:
    """Get the value of the dynamic variable `*ns*` in the current thread."""
    ns_sym = sym.Symbol(ns_var_name, ns=ns_var_ns)
    ns: Namespace = Maybe(Var.find(ns_sym)).map(lambda v: v.value).or_else_raise(
        lambda: RuntimeException(f"Dynamic Var {ns_sym} not bound!")
    )
    return ns

def resolve_alias(s: sym.Symbol, ns: Optional[Namespace] = None) -> sym.Symbol:
    """Resolve the aliased symbol in the current namespace."""
    if s in _SPECIAL_FORMS:
        return s

    ns = Maybe(ns).or_else(get_current_ns)
    if s.ns is not None:
        aliased_ns = ns.get_alias(sym.symbol(s.ns))
        if aliased_ns is not None:
            return sym.symbol(s.name, aliased_ns.name)
        else:
            return s
    else:
        which_var = ns.find(sym.symbol(s.name))
        if which_var is not None:
            return sym.symbol(which_var.name.name, which_var.ns.name)
        else:
            return sym.symbol(s.name, ns=ns.name)

def resolve_var(s: sym.Symbol, ns: Optional[Namespace] = None) -> Optional[Var]:
    """Resolve the aliased symbol to a Var from the specified
    namespace, or the current namespace if none is specified."""
    return Var.find(resolve_alias(s, ns))

def add_generated_python(
    generated_python: str,
    var_name: str = _GENERATED_PYTHON_VAR_NAME,
    which_ns: Optional[str] = None,
) -> None:
    """Add generated Python code to a dynamic variable in which_ns."""
    if which_ns is None:
        which_ns = get_current_ns().name
    ns_sym = sym.Symbol(var_name, ns=which_ns)
    v = Maybe(Var.find(ns_sym)).or_else(
        lambda: Var.intern(
            sym.symbol(which_ns),  # type: ignore
            sym.symbol(var_name),
            "",
            dynamic=True,
            meta=lmap.map({_PRIVATE_META_KEY: True}),
        )
    )
    v.value = v.value + generated_python

def print_generated_python(
    var_name: str = _PRINT_GENERATED_PY_VAR_NAME, core_ns_name: str = CORE_NS
) -> bool:
    """Return the value of the `*print-generated-python*` dynamic variable."""
    ns_sym = sym.Symbol(var_name, ns=core_ns_name)
    return (
        Maybe(Var.find(ns_sym))
        .map(lambda v: v.value)
        .or_else_raise(lambda: RuntimeException(f"Dynamic Var {ns_sym} not bound!"))
    )

def bootstrap(ns_var_name: str = NS_VAR_NAME, core_ns_name: str = CORE_NS) -> None:
    """Bootstrap the environment with functions that are are difficult to
    express with the very minimal lisp environment."""
    core_ns_sym = sym.symbol(core_ns_name)
    ns_var_sym = sym.symbol(ns_var_name, ns=core_ns_name)
    __NS = Maybe(Var.find(ns_var_sym)).or_else_raise(
        lambda: RuntimeException(f"Dynamic Var {ns_var_sym} not bound!")
    )

    def in_ns(s: sym.Symbol):
        ns = Namespace.get_or_create(s)
        __NS.value = ns
        return ns

    Var.intern_unbound(core_ns_sym, sym.symbol("unquote"))
    Var.intern_unbound(core_ns_sym, sym.symbol("unquote-splicing"))
    Var.intern(
        core_ns_sym, sym.symbol("in-ns"), in_ns, meta=lmap.map({_REDEF_META_KEY: True})
    )
    Var.intern(
        core_ns_sym,
        sym.symbol(_PRINT_GENERATED_PY_VAR_NAME),
        False,
        dynamic=True,
        meta=lmap.map({_PRIVATE_META_KEY: True}),
    )
    Var.intern(
        core_ns_sym,
        sym.symbol(_GENERATED_PYTHON_VAR_NAME),
        "",
        dynamic=True,
        meta=lmap.map({_PRIVATE_META_KEY: True}),
    )

    # Dynamic Vars for controlling printing
    Var.intern(
        core_ns_sym, sym.symbol(_PRINT_DUP_VAR_NAME), lobj.PRINT_DUP, dynamic=True
    )
    Var.intern(
        core_ns_sym, sym.symbol(_PRINT_LENGTH_VAR_NAME), lobj.PRINT_LENGTH, dynamic=True
    )
    Var.intern(
        core_ns_sym, sym.symbol(_PRINT_LEVEL_VAR_NAME), lobj.PRINT_LEVEL, dynamic=True
    )
    Var.intern(
        core_ns_sym, sym.symbol(_PRINT_META_VAR_NAME), lobj.PRINT_META, dynamic=True
    )
    Var.intern(
        core_ns_sym,
        sym.symbol(_PRINT_READABLY_VAR_NAME),
        lobj.PRINT_READABLY,
        dynamic=True,
    )


from typing import Optional, TypeVar, cast

from pyrsistent import PList, plist  # noqa # pylint: disable=unused-import
from pyrsistent._plist import _EMPTY_PLIST

from basilisp.lang.interfaces import IMeta, IPersistentList, IPersistentMap, ISeq
from basilisp.lang.obj import seq_lrepr as _seq_lrepr
from basilisp.lang.seq import EMPTY

T = TypeVar("T")

class List(IMeta, ISeq[T], IPersistentList[T]):  # type: ignore
    """Basilisp List. Delegates internally to a pyrsistent.PList object.

    Do not instantiate directly. Instead use the l() and list() factory
    methods below."""

    __slots__ = ("_inner", "_meta")

    def __init__(self, wrapped: "PList[T]", meta=None) -> None:
        self._inner = wrapped
        self._meta = meta

    def __eq__(self, other):
        return self._inner == other

    def __getitem__(self, item):
        if isinstance(item, slice):
            return List(self._inner[item])
        return self._inner[item]

    def __hash__(self):
        return hash(self._inner)

    def __len__(self):
        return len(self._inner)

    def _lrepr(self, **kwargs) -> str:
        return _seq_lrepr(self._inner, "(", ")", meta=self._meta, **kwargs)

    @property
    def meta(self) -> Optional[IPersistentMap]:
        return self._meta

    def with_meta(self, meta: IPersistentMap) -> "List":
        new_meta = meta if self._meta is None else self._meta.update(meta)
        return list(self._inner, meta=new_meta)

    @property
    def is_empty(self):
        return self._inner is _EMPTY_PLIST

    @property
    def first(self):
        try:
            return self._inner.first
        except AttributeError:
            return None

    @property
    def rest(self) -> ISeq:
        if self._inner.rest is _EMPTY_PLIST:  # type: ignore
            return EMPTY
        return List(self._inner.rest)  # type: ignore

    def cons(self, *elems: T) -> "List[T]":
        l = self._inner
        for elem in elems:
            l = l.cons(elem)
        return List(l)

    @staticmethod
    def empty(meta=None) -> "List":  # pylint:disable=arguments-differ
        return l(meta=meta)

    def peek(self):
        return self.first

    def pop(self) -> "List":
        if self.is_empty:
            raise IndexError("Cannot pop an empty list")
        return cast(List, self.rest)

def list(members, meta=None) -> List:  # pylint:disable=redefined-builtin
    """Creates a new list."""
    return List(  # pylint: disable=abstract-class-instantiated
        plist(iterable=members), meta=meta
    )

def l(*members, meta=None) -> List:
    """Creates a new list from members."""
    return List(  # pylint: disable=abstract-class-instantiated
        plist(iterable=members), meta=meta
    )


import logging

from jwcrypto import jwe
from jwcrypto.jwe import InvalidJWEData

from sdc.crypto.exceptions import InvalidTokenException
from sdc.crypto.helper import extract_kid_from_header

logger = logging.getLogger(__name__)

class JWEHelper:
    '''
    Helper methods for encrypting a JWS/JWT. This class assumes you have used the RSA-OAP-A256GCM alogrithm
    and the required private key is available in the keystore.
    '''
    @staticmethod
    def decrypt(encrypted_token, key_store=None, purpose=None):
        try:
            jwe_token = jwe.JWE(algs=['RSA-OAEP', 'A256GCM'])
            jwe_token.deserialize(encrypted_token)

            jwe_kid = extract_kid_from_header(encrypted_token)

            logger.info("Decrypting JWE kid is {}".format(jwe_kid))

            private_jwk = key_store.get_private_key_by_kid(purpose, jwe_kid).as_jwk()

            jwe_token.decrypt(private_jwk)

            return jwe_token.payload.decode()
        except (ValueError, InvalidJWEData) as e:
            raise InvalidTokenException(str(e)) from e

    @staticmethod
    def decrypt_with_key(encrypted_token, key):
        """
        Decrypts JWE token with supplied key
        :param encrypted_token:
        :param key: A (:class:`jwcrypto.jwk.JWK`) decryption key or a password
        :returns: The payload of the decrypted token
        """
        try:
            jwe_token = jwe.JWE(algs=['RSA-OAEP', 'A256GCM'])
            jwe_token.deserialize(encrypted_token)

            jwe_token.decrypt(key)

            return jwe_token.payload.decode()
        except (ValueError, InvalidJWEData) as e:
            raise InvalidTokenException(str(e)) from e

    @staticmethod
    def encrypt(payload, kid, key_store=None, purpose=None):
        try:
            logger.info("Encrypting JWE kid is {}".format(kid))

            public_jwk = key_store.get_public_key_by_kid(purpose, kid).as_jwk()

            protected_header = {
                "alg": "RSA-OAEP",
                "enc": "A256GCM",
                "kid": kid,
            }

            token = jwe.JWE(plaintext=payload, protected=protected_header)

            token.add_recipient(public_jwk)
        except (ValueError, InvalidJWEData) as e:
            raise InvalidTokenException(str(e)) from e

        return token.serialize(compact=True)

    @staticmethod
    def encrypt_with_key(payload, kid, key):
        try:
            logger.info("Encrypting JWE with provided key and kid {}".format(kid))

            protected_header = {
                "alg": "RSA-OAEP",
                "enc": "A256GCM",
                "kid": kid,
            }

            token = jwe.JWE(plaintext=payload, protected=protected_header)

            token.add_recipient(key)
        except (ValueError, InvalidJWEData) as e:
            raise InvalidTokenException(str(e)) from e

        return token.serialize(compact=True)


from sdc.crypto.exceptions import InvalidTokenException
from sdc.crypto.jwe_helper import JWEHelper
from sdc.crypto.jwt_helper import JWTHelper

def decrypt(token, key_store, key_purpose, leeway=120):
    """This decrypts the provided jwe token, then decodes resulting jwt token and returns
    the payload.

    :param str token: The jwe token.
    :param key_store: The key store.
    :param str key_purpose: Context for the key.
    :param int leeway: Extra allowed time in seconds after expiration to account for clock skew.
    :return: The decrypted payload.

    """
    tokens = token.split('.')
    if len(tokens) != 5:
        raise InvalidTokenException("Incorrect number of tokens")

    decrypted_token = JWEHelper.decrypt(token, key_store, key_purpose)

    payload = JWTHelper.decode(decrypted_token, key_store, key_purpose, leeway)

    return payload


from sdc.crypto.jwe_helper import JWEHelper
from sdc.crypto.jwt_helper import JWTHelper

def encrypt(json, key_store, key_purpose):
    """This encrypts the supplied json and returns a jwe token.

    :param str json: The json to be encrypted.
    :param key_store: The key store.
    :param str key_purpose: Context for the key.
    :return: A jwe token.

    """
    jwt_key = key_store.get_key_for_purpose_and_type(key_purpose, "private")

    payload = JWTHelper.encode(json, jwt_key.kid, key_store, key_purpose)

    jwe_key = key_store.get_key_for_purpose_and_type(key_purpose, "public")

    return JWEHelper.encrypt(payload, jwe_key.kid, key_store, key_purpose)


import logging

from jwcrypto import jwk

from sdc.crypto.exceptions import InvalidTokenException, CryptoError

logger = logging.getLogger(__name__)

def validate_required_keys(keys, key_purpose):

    def has_purpose_and_type(kid, key_type):
        key = keys['keys'][kid]
        return key['purpose'] == key_purpose and key['type'] == key_type

    public_keys = [kid for kid in keys['keys'] if has_purpose_and_type(kid, "public")]

    private_keys = [kid for kid in keys['keys'] if has_purpose_and_type(kid, "private")]

    if not public_keys:
        raise CryptoError("No public key loaded for purpose {}".format(key_purpose))

    if not private_keys:
        raise CryptoError("No private key loaded for purpose {}".format(key_purpose))

class Key:
    def __init__(self, kid, purpose, key_type, value):
        self.kid = kid
        self.purpose = purpose
        self.key_type = key_type
        self.value = value

    def as_jwk(self):
        return jwk.JWK.from_pem(self.value.encode('utf-8'))

class KeyStore:
    def __init__(self, keys):
        try:
            self.keys = {kid: Key(kid, key['purpose'], key['type'], key['value']) for kid, key in keys['keys'].items()}
        except KeyError as e:
            logger.warning("Missing mandatory key values")
            raise CryptoError from e

    def get_private_key_by_kid(self, purpose, kid):
        return self.get_key_by_kid(purpose, kid, "private")

    def get_public_key_by_kid(self, purpose, kid):
        return self.get_key_by_kid(purpose, kid, "public")

    def get_key_by_kid(self, purpose, kid, key_type):
        try:
            key = self.keys[kid]
            if key.purpose != purpose or key.key_type != key_type:
                raise InvalidTokenException
        except(KeyError, InvalidTokenException):
            raise InvalidTokenException("Invalid {} Key Identifier [{}] for Purpose [{}]".format(key_type, kid, purpose))
        else:
            return key

    def get_key_for_purpose_and_type(self, purpose, key_type):
        """
        Gets a list of keys that match the purpose and key_type, and returns the first key in that list
        Note, if there are many keys that match the criteria, the one you get back will be random from that list
        :returns: A key object that matches the criteria
        """
        key = [key for key in self.keys.values() if key.purpose == purpose and key.key_type == key_type]
        try:
            return key[0]
        except IndexError:
            return None


import inspect
import collections

def get_arg_count(func):
    args, *rest = inspect.getfullargspec(func)
    return len(args)

def convert_args_to_kwargs(func, args):
    args_names, *rest = inspect.getfullargspec(func)
    return dict(zip(args_names, args))

def get_default_args(func):
    """
    returns a dictionary of arg_name:default_values for the input function
    """
    args, _, _, defaults, *rest = inspect.getfullargspec(func)
    return dict(zip(reversed(args), reversed(defaults)))

def get_object_key(object_, object_key, object_tuple_key):
    """

    :param object_: Object you want to pull a value fron
    :param object_key: String or list/tuple of strings containing field names you want to generate a key from
    :param object_tuple_key: A temporary shortcut until we allow dot.path traversal for object_key.
    Will call getattr(getattr(result, join_table_name), object_key)
    :return:
    """
    if object_tuple_key:
        object_ = getattr(object_, object_tuple_key)
    if isinstance(object_key, (list, tuple)):
        return_string = ''
        for element in object_key:
            return_string += str(getattr(object_, element))
            return_string += '_'
        return return_string
    return str(getattr(object_, object_key))

def get_argument_key(kwargs, argument_key, index):
    """

    :param kwargs: keyword arguments used to kickoff the multiget
    :param argument_key: Name or set of names of keyword arguments to read from
    :param index: which set of arguments are considered
    :return:
    """
    if isinstance(argument_key, (list, tuple)):
        return_string = ''
        for element in argument_key:
            return_val = kwargs.get(element)[index]
            if return_val is None:
                return_val = ''
            return_string += str(return_val)
            if len(argument_key) > 1:
                return_string += '_'
        return return_string
    return str(kwargs.get(argument_key)[index] or '')

def map_objects_to_result(objects, object_key, object_tuple_key, result_value, default_result):
    if not callable(default_result):
        map_ = collections.defaultdict(lambda: default_result)
    else:
        map_ = collections.defaultdict(default_result)

    if not objects:
        return map_

    # This is a hack to get list-type multigets to work
    if default_result is not None and hasattr(map_['iterable'], 'append'):
        for element in objects:
            key = get_object_key(element, object_key, object_tuple_key)
            if not result_value:
                map_[key].append(element)
            elif isinstance(result_value, str):
                map_[key].append(getattr(element, result_value))
            elif isinstance(result_value, (list, tuple)):
                map_[key] = {}
                for attr in result_value:
                    map_[key][attr].append(getattr(element, attr))
    else:
        for element in objects:
            key = get_object_key(element, object_key, object_tuple_key)
            if not result_value:
                map_[key] = element
            elif isinstance(result_value, str):
                map_[key] = getattr(element, result_value)
            elif isinstance(result_value, (list, tuple)):
                map_[key] = {}
                for attr in result_value:
                    map_[key][attr] = getattr(element, attr)

    return map_

def get_request_count(arguments):
    for key, value in arguments.items():
        return len(value)

def map_arguments_to_objects(kwargs, objects, object_key, object_tuple_key, argument_key, result_value, default_result):
    """
    :param kwargs: kwargs used to call the multiget function
    :param objects: objects returned from the inner function
    :param object_key: field or set of fields that map to the kwargs provided
    :param object_tuple_key: A temporary shortcut until we allow dot.path traversal for object_key.
    Will call getattr(getattr(result, join_table_name), object_key)
    :param argument_key: field or set of fields that map to the objects provided
    :param result_value: Limit the fields returned to this field or set of fields (none = whole object)
    :param default_result: If the inner function returned none for a set of parameters, default to this
    :return:
    """

    # Map each object to the set of desired result data using a key
    # that corresponds to the parameters for ordering purposes
    map_ = map_objects_to_result(objects, object_key, object_tuple_key, result_value, default_result)
    element_count = get_request_count(kwargs)
    # Using the map we just made, return the objects in the same order
    # they were primed using the object and argument keys to match against
    return [map_[get_argument_key(kwargs, argument_key, index)] for index in range(0, element_count)]

def get_kwargs_for_function(inner_f):
    args_names, *rest = inspect.getfullargspec(inner_f)
    return args_names


import base64

from . import get_cache, function_tools

class BaseCacheWrapper(object):
    # TODO: Implement kwargs as well.
    def __init__(self, inner_f):
        self.inner_f = inner_f
        self.f_prefix = inner_f.__module__ + '.' + inner_f.__name__

    def __call__(self, *args, **kwargs):
        """Pull the return value from the request cache if possible. If not,
        pull the return value from memcache if possible. If not, pull the
        value from calling inner_f, then set the value in request cache
        and memcache where needed."""
        key = self.get_cache_key(*args, **kwargs)

        # Get the value direct from request cache if it's there.
        cache = get_cache()
        if key in cache:
            return cache[key]
        else:
            key = self.get_cache_key(*args, **kwargs)
            result = self.inner_f(*args, **kwargs)
            cache[key] = result
            return result

    def get_cache_key(self, *args, **kwargs):
        # Convert args to kwargs
        kwargs.update(function_tools.convert_args_to_kwargs(self.inner_f, args))
        args = ()

        return ':'.join(
            [self.f_prefix] +
            [self.arg_to_key(arg) for arg in args] +
            [self.arg_to_key(key + str(arg)) for key, arg in sorted(kwargs.items())]
        )

    @staticmethod
    def arg_to_key(arg):
        if isinstance(arg, int):
            return str(arg)
        elif isinstance(arg, str):
            return base64.b64encode(bytearray(arg, "utf-8")).decode('utf-8')
        elif isinstance(arg, list):
            return ','.join([BaseCacheWrapper.arg_to_key(inner_arg) for inner_arg in arg])
        elif arg is None:
            # Unlikely to collide with base64 encoded string above
            return 'value:none'
        else:
            print('trying to get a key for', str(arg))
            raise NotImplemented()

    def delete(self, *args):
        """Remove the key from the request cache and from memcache."""
        cache = get_cache()
        key = self.get_cache_key(*args)
        if key in cache:
            del cache[key]

def cached():
    def create_wrapper(inner_f):
        return BaseCacheWrapper(inner_f)

    return create_wrapper


import collections
from .base_cache_wrapper import BaseCacheWrapper
from . import function_tools, get_cache

class MultigetCacheWrapper(BaseCacheWrapper):
    def __init__(self, inner_f, object_key, argument_key, default_result,
                 result_key, object_tuple_key, coerce_args_to_strings=False):
        self.argument_tuple_list = []
        self.kwargs_dict = collections.defaultdict(list)
        self.object_key = object_key
        self.result_key = result_key
        self.default_result = default_result
        self.object_tuple_key = object_tuple_key
        self.coerce_args_to_strings = coerce_args_to_strings

        # backwards compatibility
        self.multiget = True

        self.argument_key = argument_key
        if not argument_key:
            self.argument_key = function_tools.get_kwargs_for_function(inner_f)
        super(MultigetCacheWrapper, self).__init__(inner_f)

    def __call__(self, *args, **kwargs):
        """
        Pull the return value from the request cache if possible. If not,
        pull the value from calling inner_f, then set the value in request cache
        """
        key = self.get_cache_key(*args, **kwargs)

        # Get the value direct from request cache if it's there.
        cache = get_cache()
        if key in cache:
            return cache[key]
        else:
            self.prime(*args, **kwargs)
            self._issue_gets_for_primes()
            return cache[key]

    def prime(self, *args, **kwargs):
        # Convert positional arguments into kwargs
        kwargs.update(function_tools.convert_args_to_kwargs(self.inner_f, args))
        args = ()

        # Used to calculate cache keys
        self.argument_tuple_list.append((args, kwargs.copy()))

        # Verify argument count (very light measure)
        if len(kwargs) != function_tools.get_arg_count(self.inner_f):
            raise Exception(
                'Invalid multiget args: ' + str(kwargs) + ' for function ' +
                str(self.inner_f)
            )

        # Create parallel lists for each kwarg for each object added to the queue
        for key, value in kwargs.items():
            # sometimes we get a list??
            if hasattr(value, 'append'):
                value = value[0]
            if self.coerce_args_to_strings:
                value = str(value)
            self.kwargs_dict[key].append(value)

    def _issue_gets_for_primes(self):
        cache = get_cache()

        # Only call with kwargs because we converted earlier
        objects = self.inner_f(**{key: list(set(params)) for key, params in self.kwargs_dict.items()})
        # For the objects that were returned, reorder them such that they match
        # the order they were primed in, and if nothing was returned for a set
        # of arguments, use the provided default value
        mapped_objects = function_tools.map_arguments_to_objects(
            self.kwargs_dict, objects, self.object_key, self.object_tuple_key,
            self.argument_key, self.result_key, self.default_result
        )
        for (args, kwargs), mapped_object in zip(self.argument_tuple_list, mapped_objects):
            key = self.get_cache_key(*args, **kwargs)
            cache[key] = mapped_object

        # Reset
        self.argument_tuple_list = []
        self.kwargs_dict = collections.defaultdict(list)

def multiget_cached(object_key, argument_key=None, default_result=None,
                    result_fields=None, join_table_name=None, coerce_args_to_strings=False):
    """
    :param object_key: the names of the attributes on the result object that are meant to match the function parameters
    :param argument_key: the function parameter names you wish to match with the `object_key`s.
    By default, this will be all of your wrapped function's arguments, in order.
    So, you'd really only use this when you want to ignore a given function argument.
    :param default_result: The result to put into the cache if nothing is matched.
    :param result_fields: The attribute on your result object you wish to return the value of.
    By default, the whole object is returned.
    :param join_table_name: A temporary shortcut until we allow dot.path traversal for object_key.
    Will call getattr(getattr(result, join_table_name), object_key)
    :param coerce_args_to_strings: force coerce all arguments to the inner function to strings.
    Useful for SQL where mixes of ints and strings in `WHERE x IN (list)` clauses causes poor performance.
    :return: A wrapper that allows you to queue many O(1) calls and flush the queue all at once,
    rather than executing the inner function body N times.
    """

    def create_wrapper(inner_f):
        return MultigetCacheWrapper(
            inner_f, object_key, argument_key, default_result, result_fields,
            join_table_name, coerce_args_to_strings=coerce_args_to_strings
        )

    return create_wrapper


# -*- coding: utf-8 -*-
"""
Environment discovery and helper functions for Google App Engine.
Some methods from the following modules have been made available for convenience:

* google.appengine.api.app_identity
  https://cloud.google.com/appengine/docs/python/appidentity/
* google.appengine.api.modules
  https://cloud.google.com/appengine/docs/python/modules/
* google.appengine.api.namespace_manager
  https://cloud.google.com/appengine/docs/python/multitenancy/

Example:

  import gaek.environ

  # Only send emails in production.
  if gaek.environ.is_production():
    mail.send(*args, **kwargs)

"""

__author__ = 'Eric Higgins'
__copyright__ = 'Copyright 2015-2016, Eric Higgins'
__email__ = 'erichiggins@gmail.com'

import os

from google.appengine.api import app_identity
from google.appengine.api import modules
from google.appengine.api import namespace_manager

__all__ = (
    # App Identity functions.
    'get_application_id',
    'get_default_version_hostname',
    'get_service_account_name',
    # Module functions.
    'get_current_instance_id',
    'get_current_module_name',
    'get_current_version_name',
    'get_default_version',
    'get_hostname',
    'get_modules',
    'get_versions',
    # Namespace functions.
    'get_namespace',
    'google_apps_namespace',
    # Helper functions.
    'get_dot_target_name',
    'get_dot_target_name_safe',
    'get_environ_dict',
    'is_host_google',
    'is_development',
    'is_staging',
    'is_staging_safe',
    'is_production',
    'is_production_safe',
    'is_default_version',
    'is_default_version_safe',
    'get_current_module_name_safe',
    'get_current_version_name_safe'
)

_UNDEFINED = '_UNDEFINED_'

# App Identity functions.
get_application_id = app_identity.get_application_id
get_default_version_hostname = app_identity.get_default_version_hostname
get_service_account_name = app_identity.get_service_account_name

# Module functions.
get_current_instance_id = modules.get_current_instance_id
get_current_module_name = modules.get_current_module_name
get_current_version_name = modules.get_current_version_name
get_default_version = modules.get_default_version
get_hostname = modules.get_hostname
get_modules = modules.get_modules
get_versions = modules.get_versions

# Namespace functions.
get_namespace = namespace_manager.get_namespace
google_apps_namespace = namespace_manager.google_apps_namespace

# Helper functions.
def get_current_version_name_safe():
  """Returns the current version of the app, or None if there is no current version found."""
  try:
    return modules.get_current_version_name()
  except KeyError:
    return None

def get_current_module_name_safe():
  """Returns the current module of the app, or None if there is no current module found.."""
  try:
    return modules.get_current_module_name()
  except KeyError:
    return None

def is_host_google():
  """True if the app is being hosted from Google App Engine servers."""
  return os.environ.get('SERVER_SOFTWARE', '').startswith('Google') or get_hostname().endswith('.appspot.com')

def is_default_version(version=None):
  """True if the current or specified app version is the default."""
  version = version or get_current_version_name()
  return version == get_default_version()

def is_default_version_safe(version=None):
  """
  True if the current or specified app version is the default.
  Returns False when there is no version found.
  """
  version = version or get_current_version_name_safe()
  return version == get_default_version()

def is_development():
  """True if the dev_appserver is running (localhost or local development server)."""
  return os.environ.get('SERVER_SOFTWARE', '').startswith('Development')

def is_staging(version=None):
  """True if the app is hosted by Google (appspot.com) but the version is not the default."""
  return is_host_google() and not is_default_version(version)

def is_staging_safe(version=None):
  """True if the app is hosted by Google (appspot.com) but the version is not the default."""
  is_default_version = is_default_version_safe()
  if is_default_version is None:
    return False
  return is_host_google() and not is_default_version

def is_production(version=None):
  """True if the app is being hosted by Google and the default version."""
  return is_host_google() and is_default_version(version)

def is_production_safe(version=None):
  """True if the app is being hosted by Google and the default version."""
  is_default_version = is_default_version_safe(version)
  if is_default_version is None:
    return False
  return is_host_google() and is_default_version

def get_dot_target_name(version=None, module=None):
  """Returns the current version/module in -dot- notation which is used by `target:` parameters."""
  version = version or get_current_version_name()
  module = module or get_current_module_name()
  return '-dot-'.join((version, module))

def get_dot_target_name_safe(version=None, module=None):
  """
  Returns the current version/module in -dot- notation which is used by `target:` parameters.
  If there is no current version or module then None is returned.
  """
  version = version or get_current_version_name_safe()
  module = module or get_current_module_name_safe()
  if version and module:
    return '-dot-'.join((version, module))
  return None

def _get_os_environ_dict(keys):
  """Return a dictionary of key/values from os.environ."""
  return {k: os.environ.get(k, _UNDEFINED) for k in keys}

def _get_app_identity_dict(keys):
  """Return a dictionary of key/values from the app_identity module functions."""
  return {k: getattr(app_identity, k)() for k in keys}

def _get_modules_dict(keys):
  """Return a dictionary of key/values from the modules module functions."""
  return {k: getattr(modules, k)() for k in keys}

def _get_namespace_manager_dict(keys):
  """Return a dictionary of key/values from the namespace_manager module functions."""
  return {k: getattr(namespace_manager, k)() for k in keys}

def get_environ_dict():
  """Return a dictionary of all environment keys/values."""
  return {
      'os.environ': _get_os_environ_dict((
          'AUTH_DOMAIN',
          'CURRENT_CONFIGURATION_VERSION',
          'CURRENT_MODULE_ID',
          'CURRENT_VERSION_ID',
          'DEFAULT_VERSION_HOSTNAME',
          'FEDERATED_IDENTITY',
          'FEDERATED_PROVIDER',
          'GAE_LOCAL_VM_RUNTIME',
          'HTTP_HOST',
          'HTTP_PROXY',
          'HTTP_X_APPENGINE_HTTPS',
          'HTTP_X_APPENGINE_QUEUENAME',
          'HTTP_X_ORIGINAL_HOST',
          'HTTP_X_ORIGINAL_SCHEME',
          'SERVER_NAME',
          'SERVER_PORT',
          'SERVER_SOFTWARE',
          'USER_IS_ADMIN',
      )),
      'app_identity': _get_app_identity_dict((
          'get_service_account_name',
          'get_application_id',
          'get_default_version_hostname',
      )),
      'modules': _get_modules_dict((
          'get_current_module_name',
          'get_current_version_name',
          'get_current_instance_id',
          'get_modules',
          'get_versions',
          'get_default_version',
          'get_hostname',
      )),
      'namespace_manager': _get_namespace_manager_dict((
          'get_namespace',
          'google_apps_namespace',
      )),
  }


from functools import partial
from inspect import getmodule, getmembers, isclass, isroutine
from typeguard import check_argument_types
from typing import Callable

def name(obj) -> str:
	"""This helper function attempts to resolve the dot-colon import path for a given object.

	Specifically searches for classes and methods, it should be able to find nearly anything at either the module
	level or nested one level deep.  Uses ``__qualname__`` if available.
	"""

	if not isroutine(obj) and not hasattr(obj, '__name__') and hasattr(obj, '__class__'):
		obj = obj.__class__

	module = getmodule(obj)

	return module.__name__ + ':' + obj.__qualname__


# -*- coding: utf-8 -*-
"""
The FIQL ``Constraint`` is the building block of the FIQL ``Expression``. A
FIQL ``Constraint`` is, on it's own, a very simple ``Expression``.

The ``constraint`` module includes the code used for managing comparison
acceptance and representation of the FIQL ``Constraint``.

Attributes:
    COMPARISON_MAP (dict): Mappings for common FIQL comparisons.
"""
from __future__ import unicode_literals
from __future__ import absolute_import

try:
    #pylint: disable=no-name-in-module
    from urllib import quote_plus
except ImportError:
    #pylint: disable=import-error,no-name-in-module
    from urllib.parse import quote_plus

from .exceptions import FiqlObjectException
from .constants import COMPARISON_COMP
from .expression import BaseExpression, Expression

# Common FIQL comparisons.
COMPARISON_MAP = {
    '==': '==',
    '!=': '!=',
    '=gt=': '>',
    '=ge=': '>=',
    '=lt=': '<',
    '=le=': '<=',
}

class Constraint(BaseExpression):

    """
    The ``Constraint`` is the smallest logical unit for a FIQL ``Expression``.
    It itself must evaluate to ``True`` or ``False`` and contains no smaller
    unit which itself can evaulate to ``True`` or ``False``.

    Attributes:
        selector (string): Constraint ``selector``.
        comparison (string): Constraint ``comparison`` operator.
        argument (string): Constraint ``argument``.
    """

    def __init__(self, selector, comparison=None, argument=None):
        """Initialize instance of ``Constraint``.

        Args:
            selector (string): URL decoded constraint ``selector``.
            comparison (string, optional): Parsed/mapped ``comparison``
                operator. Defaults to ``None``.
            argument (string, optional): URL decoded constraint ``argument``.
                Defaults to ``None``.

        Raises:
            FiqlObjectException: Not a valid FIQL comparison.
        """
        super(Constraint, self).__init__()
        self.selector = selector
        # Validate comparison format.
        if comparison and COMPARISON_COMP.match(comparison) is None:
            raise FiqlObjectException(
                "'%s' is not a valid FIQL comparison" % comparison)
        self.comparison = comparison
        self.argument = argument

    def op_and(self, *elements):
        """Create an ``Expression`` using this ``Constraint`` and the specified
        additional ``elements`` joined using an "AND" ``Operator``

        Args:
            *elements (BaseExpression): The ``Expression`` and/or
                ``Constraint`` elements which the "AND" ``Operator`` applies
                to in addition to this ``Constraint``.

        Returns:
            Expression: Newly created ``Expression`` including this
                ``Constraint``, the elements passed in, and the "AND"
                ``Operator``.
        """
        return Expression().op_and(self, *elements)

    def op_or(self, *elements):
        """Create an ``Expression`` using this ``Constraint`` and the specified
        additional ``elements`` joined using an "OR" ``Operator``

        Args:
            *elements (BaseExpression): The ``Expression`` and/or
                ``Constraint`` elements which the "OR" ``Operator`` applies
                to in addition to this ``Constraint``.

        Returns:
            Expression: Newly created ``Expression`` including this
                ``Constraint``, the elements passed in, and the "OR"
                ``Operator``.
        """
        return Expression().op_or(self, *elements)

    def to_python(self):
        """Deconstruct the ``Constraint`` instance to a tuple.

        Returns:
            tuple: The deconstructed ``Constraint``.
        """
        return (
            self.selector,
            COMPARISON_MAP.get(self.comparison, self.comparison),
            self.argument
        )

    def __str__(self):
        """Represent the ``Constraint`` instance as a string.

        Returns:
            string: The represented ``Constraint``.
        """
        if self.argument:
            return "{0}{1}{2}".format(quote_plus(self.selector),
                                      self.comparison,
                                      quote_plus(self.argument))
        return self.selector


"""Provide an interface using asyncio to the CAM server."""
import asyncio
from collections import OrderedDict

from async_timeout import timeout as async_timeout

from leicacam.cam import BaseCAM, _parse_receive, check_messages

class AsyncCAM(BaseCAM):
    """Driver for LASAF Computer Assisted Microscopy using asyncio."""

    def __init__(self, *args, loop=None, **kwargs):
        """Set up instance."""
        super().__init__(*args, **kwargs)
        self.loop = loop or asyncio.get_event_loop()
        self.reader = None
        self.writer = None
        self.welcome_msg = None

    async def connect(self):
        """Connect to LASAF through a CAM-socket."""
        self.reader, self.writer = await asyncio.open_connection(
            self.host, self.port, loop=self.loop)
        self.welcome_msg = await self.reader.read(self.buffer_size)

    async def send(self, commands):
        """Send commands to LASAF through CAM-socket.

        Parameters
        ----------
        commands : list of tuples or bytes string
            Commands as a list of tuples or a bytes string. cam.prefix is
            allways prepended before sending.

        Returns
        -------
        int
            Bytes sent.

        Example
        -------
        ::

            >>> # send list of tuples
            >>> await cam.send([('cmd', 'enableall'), ('value', 'true')])

            >>> # send bytes string
            >>> await cam.send(b'/cmd:enableall /value:true')

        """
        msg = self._prepare_send(commands)
        self.writer.write(msg)
        await self.writer.drain()

    async def receive(self):
        """Receive message from socket interface as list of OrderedDict."""
        try:
            incomming = await self.reader.read(self.buffer_size)
        except OSError:
            return []

        return _parse_receive(incomming)

    async def wait_for(self, cmd, value=None, timeout=60):
        """Hang until command is received.

        If value is supplied, it will hang until ``cmd:value`` is received.

        Parameters
        ----------
        cmd : string
            Command to wait for in bytestring from microscope CAM interface. If
            ``value`` is falsey, value of received command does not matter.
        value : string
            Wait until ``cmd:value`` is received.
        timeout : int
            Minutes to wait for command. If timeout is reached, an empty
            OrderedDict will be returned.

        Returns
        -------
        collections.OrderedDict
            Last received messsage or empty message if timeout is reached.

        """
        try:
            async with async_timeout(timeout * 60):
                while True:
                    msgs = await self.receive()
                    msg = check_messages(msgs, cmd, value=value)
                    if msg:
                        return msg
        except asyncio.TimeoutError:
            return OrderedDict()

    def close(self):
        """Close stream."""
        if self.writer.can_write_eof():
            self.writer.write_eof()
        self.writer.close()


from threading import RLock
from collections.abc import MutableMapping
from typeguard import check_argument_types
from typing import Callable

from .loader import traverse, load

sentinel = object()

class lazy:
	"""Lazily record the result of evaluating a function and cache the result.

	This is a non-data descriptor which tells Python to allow the instance `__dict__` to override, naturally caching
	the result. As a consequence of this assignment, whatever name is given to the lazy property must be included in
	the class' `__slots__` declaration, if one is given.

	Use as a decorator just like `@property`:

		class MyClass:
			@lazy
			def myattr(self):
				print("Executed!")
				return 42

		obj = MyClass()
		assert obj.myattr == 42 # Executed!
		assert obj.myattr == 42 # Not.
	"""

	def __init__(self, func:Callable[[object], None], name:str=None, doc:str=None):
		assert check_argument_types()

		self.__name__ = name or func.__name__
		self.__module__ = func.__module__
		self.__doc__ = func.__doc__
		self.lock = RLock()
		self.func = func

	def __repr__(self):
		return "lazy(" + repr(self.func) + ")"

	def __get__(self, instance, type=None):
		if instance is None:  # Allow direct access to the non-data descriptor via the class.
			return self

		with self.lock:  # Try to avoid situations with parallel thread access hammering the function.
			value = instance.__dict__.get(self.__name__, sentinel)

			if value is sentinel:
				value = instance.__dict__[self.__name__] = self.func(instance)

		return value

def lazyload(reference: str, *args, **kw):
	"""Lazily load and cache an object reference upon dereferencing.

	Assign the result of calling this function with either an object reference passed in positionally:

		class MyClass:
			debug = lazyload('logging:debug')

	Or the attribute path to traverse (using `marrow.package.loader:traverse`) prefixed by a period.

		class AnotherClass:
			target = 'logging:info'
			log = lazyload('.target')

	Additional arguments are passed to the eventual call to `load()`.
	"""

	assert check_argument_types()

	def lazily_load_reference(self):
		ref = reference

		if ref.startswith('.'):
			ref = traverse(self, ref[1:])

		return load(ref, *args, **kw)

	return lazy(lazily_load_reference)


# -*- coding: utf-8 -*-
"""
The ``parser`` module includes the code used to convert a string representing
a FIQL ``Expression`` into an object representing the same FIQL
``Expression``.

The ``Expression`` object returned is ideally suited for use in filtering
database queries with many ORMs.
"""
from __future__ import unicode_literals
from __future__ import absolute_import

try:
    #pylint: disable=no-name-in-module
    from urllib import unquote_plus
except ImportError:
    #pylint: disable=import-error,no-name-in-module
    from urllib.parse import unquote_plus

from .constants import CONSTRAINT_COMP
from .exceptions import FiqlFormatException
from .expression import BaseExpression, Expression
from .constraint import Constraint
from .operator import Operator

def iter_parse(fiql_str):
    """Iterate through the FIQL string. Yield a tuple containing the
    following FIQL components for each iteration:

      - preamble: Any operator or opening/closing paranthesis preceding a
        constraint or at the very end of the FIQL string.
      - selector: The selector portion of a FIQL constraint or ``None`` if
        yielding the last portion of the string.
      - comparison: The comparison portion of a FIQL constraint or ``None``
        if yielding the last portion of the string.
      - argument: The argument portion of a FIQL constraint or ``None`` if
        yielding the last portion of the string.

    For usage see :func:`parse_str_to_expression`.

    Args:
        fiql_str (string): The FIQL formatted string we want to parse.

    Yields:
        tuple: Preamble, selector, comparison, argument.
    """
    while len(fiql_str):
        constraint_match = CONSTRAINT_COMP.split(fiql_str, 1)
        if len(constraint_match) < 2:
            yield (constraint_match[0], None, None, None)
            break
        yield (
            constraint_match[0],
            unquote_plus(constraint_match[1]),
            constraint_match[4],
            unquote_plus(constraint_match[6]) \
                    if constraint_match[6] else None
        )
        fiql_str = constraint_match[8]

def parse_str_to_expression(fiql_str):
    """Parse a FIQL formatted string into an ``Expression``.

    Args:
        fiql_str (string): The FIQL formatted string we want to parse.

    Returns:
        Expression: An ``Expression`` object representing the parsed FIQL
        string.

    Raises:
        FiqlFormatException: Unable to parse string due to incorrect
            formatting.

    Example:

        >>> expression = parse_str_to_expression(
        ...         "name==bar,dob=gt=1990-01-01")

    """
    #pylint: disable=too-many-branches
    nesting_lvl = 0
    last_element = None
    expression = Expression()
    for (preamble, selector, comparison, argument) in iter_parse(fiql_str):
        if preamble:
            for char in preamble:
                if char == '(':
                    if isinstance(last_element, BaseExpression):
                        raise FiqlFormatException(
                            "%s can not be followed by %s" % (
                                last_element.__class__, Expression))
                    expression = expression.create_nested_expression()
                    nesting_lvl += 1
                elif char == ')':
                    expression = expression.get_parent()
                    last_element = expression
                    nesting_lvl -= 1
                else:
                    if not expression.has_constraint():
                        raise FiqlFormatException(
                            "%s proceeding initial %s" % (
                                Operator, Constraint))
                    if isinstance(last_element, Operator):
                        raise FiqlFormatException(
                            "%s can not be followed by %s" % (
                                Operator, Operator))
                    last_element = Operator(char)
                    expression = expression.add_operator(last_element)
        if selector:
            if isinstance(last_element, BaseExpression):
                raise FiqlFormatException("%s can not be followed by %s" % (
                    last_element.__class__, Constraint))
            last_element = Constraint(selector, comparison, argument)
            expression.add_element(last_element)
    if nesting_lvl != 0:
        raise FiqlFormatException(
            "At least one nested expression was not correctly closed")
    if not expression.has_constraint():
        raise FiqlFormatException(
            "Parsed string '%s' contained no constraint" % fiql_str)
    return expression


# -*- coding: utf-8 -*-
"""
JSON encoder/decoder adapted for use with Google App Engine NDB.

Usage:

  import ndb_json

  # Serialize an ndb.Query into an array of JSON objects.
  query = models.MyModel.query()
  query_json = ndb_json.dumps(query)

  # Convert into a list of Python dictionaries.
  query_dicts = ndb_json.loads(query_json)

  # Serialize an ndb.Model instance into a JSON object.
  entity = query.get()
  entity_json = ndb_json.dumps(entity)

  # Convert into a Python dictionary.
  entity_dict = ndb_json.loads(entity_json)

Dependencies:

  - dateutil: https://pypi.python.org/pypi/python-dateutil
"""

__author__ = 'Eric Higgins'
__copyright__ = 'Copyright 2013-2016, Eric Higgins'
__email__ = 'erichiggins@gmail.com'

import base64
import datetime
import json
import time
import types

import dateutil.parser
from google.appengine.ext import ndb

__all__ = (
    'dump',
    'dumps',
    'loads',
    'NdbDecoder',
    'NdbEncoder',
)

def encode_model(obj):
  """Encode objects like ndb.Model which have a `.to_dict()` method."""
  obj_dict = obj.to_dict()
  for key, val in obj_dict.iteritems():
    if isinstance(val, types.StringType):
      try:
        unicode(val)
      except UnicodeDecodeError:
        # Encode binary strings (blobs) to base64.
        obj_dict[key] = base64.b64encode(val)
  return obj_dict

def encode_generator(obj):
  """Encode generator-like objects, such as ndb.Query."""
  return list(obj)

def encode_key_as_entity(obj):
  """Get the Entity from the ndb.Key for further encoding."""
  # NOTE(erichiggins): Potentially poor performance for Models w/ many KeyProperty properties.
  # NOTE(ronufryk): Potentially can cause circular references and "RuntimeError: maximum recursion depth exceeded"
  return obj.get_async()

# Alias for backward-compatibility
encode_key = encode_key_as_entity

def encode_key_as_pair(obj):
  """Get the ndb.Key as a tuple of (kind, id) pairs."""
  return obj.pairs()

def encode_key_as_urlsafe(obj):
  """Get the ndb.Key as URL-safe base64-encoded string."""
  return obj.urlsafe()

def encode_future(obj):
  """Encode an ndb.Future instance."""
  return obj.get_result()

def encode_datetime(obj):
  """Encode a datetime.datetime or datetime.date object as an ISO 8601 format string."""
  # Reformat the date slightly for better JS compatibility.
  # Offset-naive dates need 'Z' appended for JS.
  # datetime.date objects don't have or need tzinfo, so don't append 'Z'.
  zone = '' if getattr(obj, 'tzinfo', True) else 'Z'
  return obj.isoformat() + zone

def encode_complex(obj):
  """Convert a complex number object into a list containing the real and imaginary values."""
  return [obj.real, obj.imag]

def encode_basevalue(obj):
  """Retrieve the actual value from a ndb.model._BaseValue.

  This is a convenience function to assist with the following issue:
  https://code.google.com/p/appengine-ndb-experiment/issues/detail?id=208
  """
  return obj.b_val

NDB_TYPE_ENCODING = {
  ndb.MetaModel: encode_model,
  ndb.Query: encode_generator,
  ndb.QueryIterator: encode_generator,
  ndb.Key: encode_key_as_entity,
  ndb.Future: encode_future,
  datetime.date: encode_datetime,
  datetime.datetime: encode_datetime,
  time.struct_time: encode_generator,
  types.ComplexType: encode_complex,
  ndb.model._BaseValue: encode_basevalue,
}

# Sort the types so any iteration is in a deterministic order
NDB_TYPES = sorted(NDB_TYPE_ENCODING.keys(), key=lambda t: t.__name__)

class NdbDecoder(json.JSONDecoder):
  """Extend the JSON decoder to add support for datetime objects."""

  def __init__(self, **kwargs):
    """Override the default __init__ in order to specify our own parameters."""
    json.JSONDecoder.__init__(self, object_hook=self.object_hook_handler, **kwargs)

  def object_hook_handler(self, val):
    """Handles decoding of nested date strings."""
    return {k: self.decode_date(v) for k, v in val.iteritems()}

  def decode_date(self, val):
    """Tries to decode strings that look like dates into datetime objects."""
    if isinstance(val, basestring) and val.count('-') == 2 and len(val) > 9:
      try:
        dt = dateutil.parser.parse(val)
        # Check for UTC.
        if val.endswith(('+00:00', '-00:00', 'Z')):
          # Then remove tzinfo for gae, which is offset-naive.
          dt = dt.replace(tzinfo=None)
        return dt
      except (TypeError, ValueError):
        pass
    return val

  def decode(self, val):
    """Override of the default decode method that also uses decode_date."""
    # First try the date decoder.
    new_val = self.decode_date(val)
    if val != new_val:
      return new_val
    # Fall back to the default decoder.
    return json.JSONDecoder.decode(self, val)

class NdbEncoder(json.JSONEncoder):
  """Extend the JSON encoder to add support for NDB Models."""

  def __init__(self, **kwargs):
    self._ndb_type_encoding = NDB_TYPE_ENCODING.copy()

    keys_as_entities = kwargs.pop('ndb_keys_as_entities', False)
    keys_as_pairs = kwargs.pop('ndb_keys_as_pairs', False)
    keys_as_urlsafe = kwargs.pop('ndb_keys_as_urlsafe', False)

    # Validate that only one of three flags is True
    if ((keys_as_entities and keys_as_pairs)
        or (keys_as_entities and keys_as_urlsafe)
        or (keys_as_pairs and keys_as_urlsafe)):
      raise ValueError('Only one of arguments ndb_keys_as_entities, ndb_keys_as_pairs, ndb_keys_as_urlsafe can be True')

    if keys_as_pairs:
      self._ndb_type_encoding[ndb.Key] = encode_key_as_pair
    elif keys_as_urlsafe:
      self._ndb_type_encoding[ndb.Key] = encode_key_as_urlsafe
    else:
      self._ndb_type_encoding[ndb.Key] = encode_key_as_entity

    json.JSONEncoder.__init__(self, **kwargs)

  def default(self, obj):
    """Overriding the default JSONEncoder.default for NDB support."""
    obj_type = type(obj)
    # NDB Models return a repr to calls from type().
    if obj_type not in self._ndb_type_encoding:
      if hasattr(obj, '__metaclass__'):
        obj_type = obj.__metaclass__
      else:
        # Try to encode subclasses of types
        for ndb_type in NDB_TYPES:
          if isinstance(obj, ndb_type):
            obj_type = ndb_type
            break

    fn = self._ndb_type_encoding.get(obj_type)

    if fn:
      return fn(obj)

    return json.JSONEncoder.default(self, obj)

def dumps(ndb_model, **kwargs):
  """Custom json dumps using the custom encoder above."""
  return NdbEncoder(**kwargs).encode(ndb_model)

def dump(ndb_model, fp, **kwargs):
  """Custom json dump using the custom encoder above."""
  for chunk in NdbEncoder(**kwargs).iterencode(ndb_model):
    fp.write(chunk)

def loads(json_str, **kwargs):
  """Custom json loads function that converts datetime strings."""
  return NdbDecoder(**kwargs).decode(json_str)