"""Test parsing functions."""

import operator
import unittest

from pydap.exceptions import ConstraintExpressionError
from pydap.parsers import (
    SimpleParser,
    parse_ce,
    parse_hyperslab,
    parse_projection,
    parse_selection,
)
from pydap.tests.datasets import VerySimpleSequence


class TestParseProjection(unittest.TestCase):
    """Test projection parser.

    The projection is a comma separated list of variable ids (or names, if the
    client is using the shorthand notation), with optional hyperslabs and
    possibly function calls.

    """

    def test_basic(self):
        """Test a basic projection."""
        parsed = parse_projection("a,b,c")
        self.assertEqual(parsed, [[("a", ())], [("b", ())], [("c", ())]])

    def test_deep(self):
        """Test a projection with a deep id."""
        parsed = parse_projection("a.b.c,d")
        self.assertEqual(parsed, [[("a", ()), ("b", ()), ("c", ())], [("d", ())]])

    def test_hyperslab(self):
        """Test a projection with a hyperslab."""
        parsed = parse_projection("a,b.c[0:2:9]")
        self.assertEqual(parsed, [[("a", ())], [("b", ()), ("c", (slice(0, 10, 2),))]])

    def test_function_call(self):
        """Test a projection with a function call."""
        parsed = parse_projection("mean(a[0],1)")
        self.assertEqual(parsed, ["mean(a[0],1)"])

    def test_nested_function_call(self):
        """Test a projection with a function call."""
        parsed = parse_projection("mean(mean(a[0],1))")
        self.assertEqual(parsed, ["mean(mean(a[0],1))"])


class TestParseSelection(unittest.TestCase):
    """Test selection parser."""

    def test_simple(self):
        """Test a simple selection."""
        id1, op, id2 = parse_selection("sequence.byte>1", VerySimpleSequence)
        self.assertIs(id1, VerySimpleSequence.sequence.byte)
        self.assertIs(op, operator.gt)
        self.assertEqual(id2, 1)

    def test_inverted(self):
        """Test an inverted selection."""
        id1, op, id2 = parse_selection("1<sequence.byte", VerySimpleSequence)
        self.assertEqual(id1, 1)
        self.assertIs(op, operator.lt)
        self.assertIs(id2, VerySimpleSequence.sequence.byte)


class TestParseCe(unittest.TestCase):
    """Test the constraint expression parser."""

    def test_empty(self):
        """Test no constraint expression."""
        projection, selection = parse_ce("")
        self.assertEqual(projection, [])
        self.assertEqual(selection, [])

    def test_basic(self):
        """Test a basic constraint expression."""
        projection, selection = parse_ce("a,b&c>1")
        self.assertEqual(projection, [[("a", ())], [("b", ())]])
        self.assertEqual(selection, ["c>1"])

    def test_only_projection(self):
        """Test a constraint expression with only the projection."""
        projection, selection = parse_ce("a,b")
        self.assertEqual(projection, [[("a", ())], [("b", ())]])
        self.assertEqual(selection, [])

    def test_only_selection(self):
        """Test a constraint expression with only the selection."""
        projection, selection = parse_ce("c>1")
        self.assertEqual(projection, [])
        self.assertEqual(selection, ["c>1"])

    def test_complex(self):
        """Test a more complex constraint expression."""
        projection, selection = parse_ce("a,b[0:2:9],c&a>1&b<2")
        self.assertEqual(
            projection, [[("a", ())], [("b", (slice(0, 10, 2),))], [("c", ())]]
        )
        self.assertEqual(selection, ["a>1", "b<2"])

    def test_function(self):
        """Test a constraint expression with a function call."""
        projection, selection = parse_ce(
            "time&bounds(0,360,-90,90,0,500,00Z01JAN1970,00Z04JAN1970)"
        )
        self.assertEqual(projection, [[("time", ())]])
        self.assertEqual(
            selection, ["bounds(0,360,-90,90,0,500,00Z01JAN1970,00Z04JAN1970)"]
        )

    def test_function_no_selection(self):
        """Test a constraint expression with a function call."""
        projection, selection = parse_ce(
            "time,bounds(0,360,-90,90,0,500,00Z01JAN1970,00Z04JAN1970)"
        )
        self.assertEqual(
            projection,
            [[("time", ())], "bounds(0,360,-90,90,0,500,00Z01JAN1970,00Z04JAN1970)"],
        )
        self.assertEqual(selection, [])

    def test_dap4_ce(self):
        protocol = "dap4"
        projection, sel = parse_ce("dap4.ce=/lon[100:1:199]", protocol)
        self.assertEqual(projection, [[("/lon", (slice(100, 200, 1),))]])
        self.assertEqual(sel, [])

    def test_inconsistent_CE_protocol(self):
        query1 = "dap4.ce=/lon[100:1:199]"
        query2 = "lon[100:1:199]"
        with self.assertRaises(ConstraintExpressionError):
            parse_ce(query1, "dap2")
            parse_ce(query2, "dap4")


class TestParseHyperslab(unittest.TestCase):
    """Test hyperslab parser."""

    def test_point(self):
        """Test a single value selection."""
        self.assertEqual(parse_hyperslab("[0]"), (slice(0, 1, 1),))

    def test_start_stop(self):
        """Test start and stop, default step."""
        self.assertEqual(parse_hyperslab("[0:1]"), (slice(0, 2, 1),))

    def test_start_step_stop(self):
        """Test start, step and stop."""
        self.assertEqual(parse_hyperslab("[0:2:9]"), (slice(0, 10, 2),))

    def test_invalid(self):
        """Test invalid hyperslab."""
        with self.assertRaises(ConstraintExpressionError):
            parse_hyperslab("[0:2:9:1]")

    def test_ndimensionsal(self):
        """Test n-dimensional slices."""
        self.assertEqual(
            parse_hyperslab("[0:2:9][0][0:99]"),
            (slice(0, 10, 2), slice(0, 1, 1), slice(0, 100, 1)),
        )


class TestSimpleParser(unittest.TestCase):
    """Test the base class for the DAS/DDS parsers."""

    def test_peek_existing(self):
        """Test the peek method when the token exists."""
        parser = SimpleParser("Hello, World!")
        self.assertEqual(parser.peek(r"\w+"), "Hello")

    def test_peek_missing(self):
        """Test the peek method when the token does not exist."""
        parser = SimpleParser("Hello, World!")
        self.assertEqual(parser.peek(r"\d+"), "")

    def test_consume_existing(self):
        """Test the consume method when the token exists."""
        parser = SimpleParser("Hello, World!")
        self.assertEqual(parser.consume(r"\w+"), "Hello")
        self.assertEqual(parser.consume(", "), ", ")
        self.assertEqual(parser.consume(r"\w+"), "World")
        self.assertEqual(parser.consume("!"), "!")

    def test_consume_missing(self):
        """Test the consume method when the token does not exist."""
        parser = SimpleParser("Hello, World!")
        with self.assertRaises(Exception):
            parser.consume(r"\d+")
