import pytest

from pathod import language
from pathod.language import base, exceptions


def parse_request(s):
    return language.parse_pathoc(s).next()


def test_times():
    reqs = list(language.parse_pathoc("get:/:x5"))
    assert len(reqs) == 5
    assert not reqs[0].times


def test_caseless_literal():
    class CL(base.CaselessLiteral):
        TOK = "foo"
    v = CL("foo")
    assert v.expr()
    assert v.values(language.Settings())


class TestTokValueNakedLiteral:

    def test_expr(self):
        v = base.TokValueNakedLiteral("foo")
        assert v.expr()

    def test_spec(self):
        v = base.TokValueNakedLiteral("foo")
        assert v.spec() == repr(v) == "foo"

        v = base.TokValueNakedLiteral("f\x00oo")
        assert v.spec() == repr(v) == r"f\x00oo"


class TestTokValueLiteral:

    def test_expr(self):
        v = base.TokValueLiteral("foo")
        assert v.expr()
        assert v.val == b"foo"

        v = base.TokValueLiteral("foo\n")
        assert v.expr()
        assert v.val == b"foo\n"
        assert repr(v)

    def test_spec(self):
        v = base.TokValueLiteral("foo")
        assert v.spec() == r"'foo'"

        v = base.TokValueLiteral("f\x00oo")
        assert v.spec() == repr(v) == r"'f\x00oo'"

        v = base.TokValueLiteral('"')
        assert v.spec() == repr(v) == """  '"'  """.strip()

        # While pyparsing has a escChar argument for QuotedString,
        # escChar only performs scapes single-character escapes and does not work for e.g. r"\x02".
        # Thus, we cannot use that option, which means we cannot have single quotes in strings.
        # To fix this, we represent single quotes as r"\x07".
        v = base.TokValueLiteral("'")
        assert v.spec() == r"'\x27'"

    def roundtrip(self, spec):
        e = base.TokValueLiteral.expr()
        v = base.TokValueLiteral(spec)
        v2 = e.parseString(v.spec())
        assert v.val == v2[0].val
        assert v.spec() == v2[0].spec()

    def test_roundtrip(self):
        self.roundtrip("'")
        self.roundtrip(r"\'")
        self.roundtrip("a")
        self.roundtrip("\"")
        # self.roundtrip("\\")
        self.roundtrip("200:b'foo':i23,'\\''")
        self.roundtrip("\a")


class TestTokValueGenerate:

    def test_basic(self):
        v = base.TokValue.parseString("@10b")[0]
        assert v.usize == 10
        assert v.unit == "b"
        assert v.bytes() == 10
        v = base.TokValue.parseString("@10")[0]
        assert v.unit == "b"
        v = base.TokValue.parseString("@10k")[0]
        assert v.bytes() == 10240
        v = base.TokValue.parseString("@10g")[0]
        assert v.bytes() == 1024 ** 3 * 10

        v = base.TokValue.parseString("@10g,digits")[0]
        assert v.datatype == "digits"
        g = v.get_generator({})
        assert g[:100]

        v = base.TokValue.parseString("@10,digits")[0]
        assert v.unit == "b"
        assert v.datatype == "digits"

    def test_spec(self):
        v = base.TokValueGenerate(1, "b", "bytes")
        assert v.spec() == repr(v) == "@1"

        v = base.TokValueGenerate(1, "k", "bytes")
        assert v.spec() == repr(v) == "@1k"

        v = base.TokValueGenerate(1, "k", "ascii")
        assert v.spec() == repr(v) == "@1k,ascii"

        v = base.TokValueGenerate(1, "b", "ascii")
        assert v.spec() == repr(v) == "@1,ascii"

    def test_freeze(self):
        v = base.TokValueGenerate(100, "b", "ascii")
        f = v.freeze(language.Settings())
        assert len(f.val) == 100


class TestTokValueFile:

    def test_file_value(self):
        v = base.TokValue.parseString("<'one two'")[0]
        assert str(v)
        assert v.path == "one two"

        v = base.TokValue.parseString("<path")[0]
        assert v.path == "path"

    def test_access_control(self, tmpdir):
        v = base.TokValue.parseString("<path")[0]
        f = tmpdir.join("path")
        f.write(b"x" * 10000)

        assert v.get_generator(language.Settings(staticdir=str(tmpdir)))

        v = base.TokValue.parseString("<path2")[0]
        with pytest.raises(exceptions.FileAccessDenied):
            v.get_generator(language.Settings(staticdir=str(tmpdir)))
        with pytest.raises(Exception, match="access disabled"):
            v.get_generator(language.Settings())

        v = base.TokValue.parseString("</outside")[0]
        with pytest.raises(Exception, match="outside"):
            v.get_generator(language.Settings(staticdir=str(tmpdir)))

    def test_spec(self):
        v = base.TokValue.parseString("<'one two'")[0]
        v2 = base.TokValue.parseString(v.spec())[0]
        assert v2.path == "one two"

    def test_freeze(self):
        v = base.TokValue.parseString("<'one two'")[0]
        v2 = v.freeze({})
        assert v2.path == v.path


class TestMisc:

    def test_generators(self):
        v = base.TokValue.parseString("'val'")[0]
        g = v.get_generator({})
        assert g[:] == b"val"

    def test_value(self):
        assert base.TokValue.parseString("'val'")[0].val == b"val"
        assert base.TokValue.parseString('"val"')[0].val == b"val"
        assert base.TokValue.parseString('"\'val\'"')[0].val == b"'val'"

    def test_value2(self):
        class TT(base.Value):
            preamble = "m"
        e = TT.expr()
        v = e.parseString("m'msg'")[0]
        assert v.value.val == b"msg"

        s = v.spec()
        assert s == e.parseString(s)[0].spec()

        v = e.parseString("m@100")[0]
        v2 = v.freeze({})
        v3 = v2.freeze({})
        assert v2.value.val == v3.value.val

    def test_fixedlengthvalue(self, tmpdir):
        class TT(base.FixedLengthValue):
            preamble = "m"
            length = 4

        e = TT.expr()
        assert e.parseString("m@4")
        with pytest.raises(Exception, match="Invalid value length"):
            e.parseString("m@100")
        with pytest.raises(Exception, match="Invalid value length"):
            e.parseString("m@1")

        s = base.Settings(staticdir=str(tmpdir))
        with open(str(tmpdir.join("path")), 'wb') as f:
            f.write(b"a" * 20)
        v = e.parseString("m<path")[0]
        with pytest.raises(Exception, match="Invalid value length"):
            v.values(s)

        with open(str(tmpdir.join("path2")), 'wb') as f:
            f.write(b"a" * 4)
        v = e.parseString("m<path2")[0]
        assert v.values(s)


class TKeyValue(base.KeyValue):
    preamble = "h"

    def values(self, settings):
        return [
            self.key.get_generator(settings),
            ": ",
            self.value.get_generator(settings),
            "\r\n",
        ]


class TestKeyValue:

    def test_simple(self):
        e = TKeyValue.expr()
        v = e.parseString("h'foo'='bar'")[0]
        assert v.key.val == b"foo"
        assert v.value.val == b"bar"

        v2 = e.parseString(v.spec())[0]
        assert v2.key.val == v.key.val
        assert v2.value.val == v.value.val

        s = v.spec()
        assert s == e.parseString(s)[0].spec()

    def test_freeze(self):
        e = TKeyValue.expr()
        v = e.parseString("h@10=@10'")[0]
        v2 = v.freeze({})
        v3 = v2.freeze({})
        assert v2.key.val == v3.key.val
        assert v2.value.val == v3.value.val


def test_intfield():
    class TT(base.IntField):
        preamble = "t"
        names = {
            "one": 1,
            "two": 2,
            "three": 3
        }
        max = 4
    e = TT.expr()

    v = e.parseString("tone")[0]
    assert v.value == 1
    assert v.spec() == "tone"
    assert v.values(language.Settings())

    v = e.parseString("t1")[0]
    assert v.value == 1
    assert v.spec() == "t1"

    v = e.parseString("t4")[0]
    assert v.value == 4
    assert v.spec() == "t4"

    with pytest.raises(Exception, match="can't exceed"):
        e.parseString("t5")


def test_options_or_value():
    class TT(base.OptionsOrValue):
        options = [
            "one",
            "two",
            "three"
        ]
    e = TT.expr()
    assert e.parseString("one")[0].value.val == b"one"
    assert e.parseString("'foo'")[0].value.val == b"foo"
    assert e.parseString("'get'")[0].value.val == b"get"

    assert e.parseString("one")[0].spec() == "one"
    assert e.parseString("'foo'")[0].spec() == "'foo'"

    s = e.parseString("one")[0].spec()
    assert s == e.parseString(s)[0].spec()

    s = e.parseString("'foo'")[0].spec()
    assert s == e.parseString(s)[0].spec()

    v = e.parseString("@100")[0]
    v2 = v.freeze({})
    v3 = v2.freeze({})
    assert v2.value.val == v3.value.val


def test_integer():
    e = base.Integer.expr()
    v = e.parseString("200")[0]
    assert v.string() == b"200"
    assert v.spec() == "200"

    assert v.freeze({}).value == v.value

    class BInt(base.Integer):
        bounds = (1, 5)

    with pytest.raises(Exception, match="must be between"):
        BInt(0)
    with pytest.raises(Exception, match="must be between"):
        BInt(6)
    assert BInt(5)
    assert BInt(1)
    assert BInt(3)


class TBoolean(base.Boolean):
    name = "test"


def test_unique_name():
    b = TBoolean(True)
    assert b.unique_name


class test_boolean:
    e = TBoolean.expr()
    assert e.parseString("test")[0].value
    assert not e.parseString("-test")[0].value

    def roundtrip(s):
        e = TBoolean.expr()
        s2 = e.parseString(s)[0].spec()
        v1 = e.parseString(s)[0].value
        v2 = e.parseString(s2)[0].value
        assert s == s2
        assert v1 == v2

    roundtrip("test")
    roundtrip("-test")