# -*- coding: utf-8 -*-
"""Tests the xonsh lexer."""
from __future__ import unicode_literals, print_function
import os
import sys
import ast
import builtins
import platform
import subprocess
import contextlib
from collections import defaultdict
from collections.abc import MutableMapping

import pytest

from xonsh.environ import Env
from xonsh.base_shell import BaseShell


VER_MAJOR_MINOR = sys.version_info[:2]
VER_FULL = sys.version_info[:3]
ON_DARWIN = platform.system() == "Darwin"
ON_WINDOWS = platform.system() == "Windows"
ON_MSYS = sys.platform == "msys"
ON_CONDA = True in [
    conda in pytest.__file__.lower() for conda in ["conda", "anaconda", "miniconda"]
]
ON_TRAVIS = "TRAVIS" in os.environ and "CI" in os.environ
ON_AZURE_PIPELINES = os.environ.get("TF_BUILD", "") == "True"
print("ON_AZURE_PIPELINES", repr(ON_AZURE_PIPELINES))
print("os.environ['TF_BUILD']", repr(os.environ.get("TF_BUILD", "")))
TEST_DIR = os.path.dirname(__file__)

# pytest skip decorators
skip_if_on_conda = pytest.mark.skipif(
    ON_CONDA, reason="Conda and virtualenv _really_ hate each other"
)

skip_if_on_msys = pytest.mark.skipif(
    ON_MSYS, reason="MSYS and virtualenv _really_ hate each other"
)

skip_if_on_windows = pytest.mark.skipif(ON_WINDOWS, reason="Unix stuff")

skip_if_on_azure_pipelines = pytest.mark.skipif(
    ON_AZURE_PIPELINES, reason="not suitable for azure"
)

skip_if_on_unix = pytest.mark.skipif(not ON_WINDOWS, reason="Windows stuff")

skip_if_on_darwin = pytest.mark.skipif(ON_DARWIN, reason="not Mac friendly")

skip_if_on_travis = pytest.mark.skipif(ON_TRAVIS, reason="not Travis CI friendly")

skip_if_pre_3_8 = pytest.mark.skipif(VER_FULL < (3, 8), reason="Python >= 3.8 feature")


def sp(cmd):
    return subprocess.check_output(cmd, universal_newlines=True)


class DummyStyler:
    styles = defaultdict(str)

    highlight_color = "#ffffff"
    background_color = "#000000"


class DummyBaseShell(BaseShell):
    def __init__(self):
        self.styler = DummyStyler()


class DummyShell:
    def settitle(self):
        pass

    _shell = None

    @property
    def shell(self):
        if self._shell is None:
            self._shell = DummyBaseShell()
        return self._shell


class DummyCommandsCache:
    def locate_binary(self, name):
        return os.path.join(os.path.dirname(__file__), "bin", name)

    def predict_threadable(self, cmd):
        return True


class DummyHistory:

    last_cmd_rtn = 0
    last_cmd_out = ""

    def append(self, x):
        pass

    def flush(self, *args, **kwargs):
        pass


class DummyEnv(MutableMapping):

    DEFAULTS = {
        "XONSH_DEBUG": 1,
        "XONSH_COLOR_STYLE": "default",
    }

    def __init__(self, *args, **kwargs):
        self._d = self.DEFAULTS.copy()
        self._d.update(dict(*args, **kwargs))

    def detype(self):
        return {k: str(v) for k, v in self._d.items()}

    def __getitem__(self, k):
        if k is ...:
            return self
        else:
            return self._d[k]

    def __setitem__(self, k, v):
        assert k is not ...
        self._d[k] = v

    def __delitem__(self, k):
        assert k is not ...
        del self._d[k]

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

    def __iter__(self):
        yield from self._d

    @contextlib.contextmanager
    def swap(self, other=None, **kwargs):
        old = {}
        # single positional argument should be a dict-like object
        if other is not None:
            for k, v in other.items():
                old[k] = self.get(k, NotImplemented)
                self[k] = v
        # kwargs could also have been sent in
        for k, v in kwargs.items():
            old[k] = self.get(k, NotImplemented)
            self[k] = v
        yield self
        # restore the values
        for k, v in old.items():
            if v is NotImplemented:
                del self[k]
            else:
                self[k] = v

    def is_manually_set(self, key):
        return False


#
# Execer tools
#


def check_exec(input, **kwargs):
    builtins.__xonsh__.execer.exec(input, **kwargs)
    return True


def check_eval(input):
    builtins.__xonsh__.env = Env(
        {
            "AUTO_CD": False,
            "XONSH_ENCODING": "utf-8",
            "XONSH_ENCODING_ERRORS": "strict",
            "PATH": [],
        }
    )
    if ON_WINDOWS:
        builtins.__xonsh__.env["PATHEXT"] = [".COM", ".EXE", ".BAT", ".CMD"]
    builtins.__xonsh__.execer.eval(input)
    return True


def check_parse(input):
    tree = builtins.__xonsh__.execer.parse(input, ctx=None)
    return tree


#
# Parser tools
#


def nodes_equal(x, y):
    __tracebackhide__ = True
    assert type(x) == type(y), "Ast nodes do not have the same type: '%s' != '%s' " % (
        type(x),
        type(y),
    )
    if isinstance(x, (ast.Expr, ast.FunctionDef, ast.ClassDef)):
        assert x.lineno == y.lineno, (
            "Ast nodes do not have the same line number : %s != %s"
            % (x.lineno, y.lineno,)
        )
        assert x.col_offset == y.col_offset, (
            "Ast nodes do not have the same column offset number : %s != %s"
            % (x.col_offset, y.col_offset)
        )
    for (xname, xval), (yname, yval) in zip(ast.iter_fields(x), ast.iter_fields(y)):
        assert xname == yname, (
            "Ast nodes fields differ : %s (of type %s) != %s (of type %s)"
            % (xname, type(xval), yname, type(yval))
        )
        assert type(xval) == type(yval), (
            "Ast nodes fields differ : %s (of type %s) != %s (of type %s)"
            % (xname, type(xval), yname, type(yval))
        )
    for xchild, ychild in zip(ast.iter_child_nodes(x), ast.iter_child_nodes(y)):
        assert nodes_equal(xchild, ychild), "Ast node children differs"
    return True
