# Copyright 2020 Google LLC All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import sys
import unittest


class TestParser(unittest.TestCase):
    skip_condition = sys.version_info[0] < 3
    skip_message = "Subtests are not supported in Python 2"

    @unittest.skipIf(skip_condition, skip_message)
    def test_func(self):
        from google.cloud.spanner_dbapi.parser import FUNC
        from google.cloud.spanner_dbapi.parser import a_args
        from google.cloud.spanner_dbapi.parser import expect
        from google.cloud.spanner_dbapi.parser import func
        from google.cloud.spanner_dbapi.parser import pyfmt_str

        cases = [
            ("_91())", ")", func("_91", a_args([]))),
            ("_a()", "", func("_a", a_args([]))),
            ("___()", "", func("___", a_args([]))),
            ("abc()", "", func("abc", a_args([]))),
            (
                "AF112(%s, LOWER(%s, %s), rand(%s, %s, TAN(%s, %s)))",
                "",
                func(
                    "AF112",
                    a_args(
                        [
                            pyfmt_str,
                            func("LOWER", a_args([pyfmt_str, pyfmt_str])),
                            func(
                                "rand",
                                a_args(
                                    [
                                        pyfmt_str,
                                        pyfmt_str,
                                        func("TAN", a_args([pyfmt_str, pyfmt_str])),
                                    ]
                                ),
                            ),
                        ]
                    ),
                ),
            ),
        ]

        for text, want_unconsumed, want_parsed in cases:
            with self.subTest(text=text):
                got_unconsumed, got_parsed = expect(text, FUNC)
                self.assertEqual(got_parsed, want_parsed)
                self.assertEqual(got_unconsumed, want_unconsumed)

    @unittest.skipIf(skip_condition, skip_message)
    def test_func_fail(self):
        from google.cloud.spanner_dbapi.exceptions import ProgrammingError
        from google.cloud.spanner_dbapi.parser import FUNC
        from google.cloud.spanner_dbapi.parser import expect

        cases = [
            ("", "FUNC: `` does not begin with `a-zA-z` nor a `_`"),
            ("91", "FUNC: `91` does not begin with `a-zA-z` nor a `_`"),
            ("_91", "supposed to begin with `\\(`"),
            ("_91(", "supposed to end with `\\)`"),
            ("_.()", "supposed to begin with `\\(`"),
            ("_a.b()", "supposed to begin with `\\(`"),
        ]

        for text, wantException in cases:
            with self.subTest(text=text):
                self.assertRaisesRegex(
                    ProgrammingError, wantException, lambda: expect(text, FUNC)
                )

    def test_func_eq(self):
        from google.cloud.spanner_dbapi.parser import func

        func1 = func("func1", None)
        func2 = func("func2", None)
        self.assertFalse(func1 == object)
        self.assertFalse(func1 == func2)
        func2.name = func1.name
        func1.args = 0
        func2.args = "0"
        self.assertFalse(func1 == func2)
        func1.args = [0]
        func2.args = [0, 0]
        self.assertFalse(func1 == func2)
        func2.args = func1.args
        self.assertTrue(func1 == func2)

    @unittest.skipIf(skip_condition, skip_message)
    def test_a_args(self):
        from google.cloud.spanner_dbapi.parser import ARGS
        from google.cloud.spanner_dbapi.parser import a_args
        from google.cloud.spanner_dbapi.parser import expect
        from google.cloud.spanner_dbapi.parser import func
        from google.cloud.spanner_dbapi.parser import pyfmt_str

        cases = [
            ("()", "", a_args([])),
            ("(%s)", "", a_args([pyfmt_str])),
            ("(%s,)", "", a_args([pyfmt_str])),
            ("(%s),", ",", a_args([pyfmt_str])),
            (
                "(%s,%s, f1(%s, %s))",
                "",
                a_args(
                    [pyfmt_str, pyfmt_str, func("f1", a_args([pyfmt_str, pyfmt_str]))]
                ),
            ),
        ]

        for text, want_unconsumed, want_parsed in cases:
            with self.subTest(text=text):
                got_unconsumed, got_parsed = expect(text, ARGS)
                self.assertEqual(got_parsed, want_parsed)
                self.assertEqual(got_unconsumed, want_unconsumed)

    @unittest.skipIf(skip_condition, skip_message)
    def test_a_args_fail(self):
        from google.cloud.spanner_dbapi.exceptions import ProgrammingError
        from google.cloud.spanner_dbapi.parser import ARGS
        from google.cloud.spanner_dbapi.parser import expect

        cases = [
            ("", "ARGS: supposed to begin with `\\(`"),
            ("(", "ARGS: supposed to end with `\\)`"),
            (")", "ARGS: supposed to begin with `\\(`"),
            ("(%s,%s, f1(%s, %s), %s", "ARGS: supposed to end with `\\)`"),
        ]

        for text, wantException in cases:
            with self.subTest(text=text):
                self.assertRaisesRegex(
                    ProgrammingError, wantException, lambda: expect(text, ARGS)
                )

    def test_a_args_has_expr(self):
        from google.cloud.spanner_dbapi.parser import a_args

        self.assertFalse(a_args([]).has_expr())
        self.assertTrue(a_args([[0]]).has_expr())

    def test_a_args_eq(self):
        from google.cloud.spanner_dbapi.parser import a_args

        a1 = a_args([0])
        self.assertFalse(a1 == object())
        a2 = a_args([0, 0])
        self.assertFalse(a1 == a2)
        a1.argv = [0, 1]
        self.assertFalse(a1 == a2)
        a2.argv = [0, 1]
        self.assertTrue(a1 == a2)

    def test_a_args_homogeneous(self):
        from google.cloud.spanner_dbapi.parser import a_args
        from google.cloud.spanner_dbapi.parser import terminal

        a_obj = a_args([a_args([terminal(10**i)]) for i in range(10)])
        self.assertTrue(a_obj.homogenous())

        a_obj = a_args([a_args([[object()]]) for _ in range(10)])
        self.assertFalse(a_obj.homogenous())

    def test_a_args__is_equal_length(self):
        from google.cloud.spanner_dbapi.parser import a_args

        a_obj = a_args([])
        self.assertTrue(a_obj._is_equal_length())

    @unittest.skipIf(skip_condition, "Python 2 has an outdated iterator definition")
    @unittest.skipIf(
        skip_condition, "Python 2 does not support 0-argument super() calls"
    )
    def test_values(self):
        from google.cloud.spanner_dbapi.parser import a_args
        from google.cloud.spanner_dbapi.parser import terminal
        from google.cloud.spanner_dbapi.parser import values

        a_obj = a_args([a_args([terminal(10**i)]) for i in range(10)])
        self.assertEqual(str(values(a_obj)), "VALUES%s" % str(a_obj))

    def test_expect(self):
        from google.cloud.spanner_dbapi.parser import ARGS
        from google.cloud.spanner_dbapi.parser import expect
        from google.cloud.spanner_dbapi import exceptions

        with self.assertRaises(exceptions.ProgrammingError):
            expect(word="", token=ARGS)
        with self.assertRaises(exceptions.ProgrammingError):
            expect(word="ABC", token=ARGS)
        with self.assertRaises(exceptions.ProgrammingError):
            expect(word="(", token=ARGS)

        with self.assertRaises(exceptions.ProgrammingError):
            expect(word="", token="ABC")

    @unittest.skipIf(skip_condition, skip_message)
    def test_expect_values(self):
        from google.cloud.spanner_dbapi.parser import VALUES
        from google.cloud.spanner_dbapi.parser import a_args
        from google.cloud.spanner_dbapi.parser import expect
        from google.cloud.spanner_dbapi.parser import func
        from google.cloud.spanner_dbapi.parser import pyfmt_str
        from google.cloud.spanner_dbapi.parser import values

        cases = [
            ("VALUES ()", "", values([a_args([])])),
            ("VALUES", "", values([])),
            ("VALUES(%s)", "", values([a_args([pyfmt_str])])),
            ("    VALUES    (%s)    ", "", values([a_args([pyfmt_str])])),
            ("VALUES(%s, %s)", "", values([a_args([pyfmt_str, pyfmt_str])])),
            (
                "VALUES(%s, %s, LOWER(%s, %s))",
                "",
                values(
                    [
                        a_args(
                            [
                                pyfmt_str,
                                pyfmt_str,
                                func("LOWER", a_args([pyfmt_str, pyfmt_str])),
                            ]
                        )
                    ]
                ),
            ),
            (
                "VALUES (UPPER(%s)), (%s)",
                "",
                values(
                    [a_args([func("UPPER", a_args([pyfmt_str]))]), a_args([pyfmt_str])]
                ),
            ),
        ]

        for text, want_unconsumed, want_parsed in cases:
            with self.subTest(text=text):
                got_unconsumed, got_parsed = expect(text, VALUES)
                self.assertEqual(got_parsed, want_parsed)
                self.assertEqual(got_unconsumed, want_unconsumed)

    @unittest.skipIf(skip_condition, skip_message)
    def test_expect_values_fail(self):
        from google.cloud.spanner_dbapi.exceptions import ProgrammingError
        from google.cloud.spanner_dbapi.parser import VALUES
        from google.cloud.spanner_dbapi.parser import expect

        cases = [
            ("", "VALUES: `` does not start with VALUES"),
            (
                "VALUES(%s, %s, (%s, %s))",
                "FUNC: `\\(%s, %s\\)\\)` does not begin with `a-zA-z` nor a `_`",
            ),
            ("VALUES(%s),,", "ARGS: supposed to begin with `\\(` in `,`"),
        ]

        for text, wantException in cases:
            with self.subTest(text=text):
                self.assertRaisesRegex(
                    ProgrammingError, wantException, lambda: expect(text, VALUES)
                )
