#!/usr/bin/env python3
# Copyright 2023 The Android Open Source Project
#
# 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.

"""Unittests for the terminal module."""

import contextlib
import io
import os
import sys
import unittest

_path = os.path.realpath(__file__ + "/../..")
if sys.path[0] != _path:
    sys.path.insert(0, _path)
del _path

# We have to import our local modules after the sys.path tweak.  We can't use
# relative imports because this is an executable program, not a module.
# pylint: disable=wrong-import-position
import rh.terminal


class ColorTests(unittest.TestCase):
    """Verify behavior of Color class."""

    def setUp(self):
        os.environ.pop("NOCOLOR", None)

    def test_enabled_auto_tty(self):
        """Test automatic enable behavior based on tty."""
        stderr = io.StringIO()
        with contextlib.redirect_stderr(stderr):
            c = rh.terminal.Color()
            self.assertFalse(c.enabled)

            stderr.isatty = lambda: True
            c = rh.terminal.Color()
            self.assertTrue(c.enabled)

    def test_enabled_auto_env(self):
        """Test automatic enable behavior based on $NOCOLOR."""
        stderr = io.StringIO()
        with contextlib.redirect_stderr(stderr):
            os.environ["NOCOLOR"] = "yes"
            c = rh.terminal.Color()
            self.assertFalse(c.enabled)

            os.environ["NOCOLOR"] = "no"
            c = rh.terminal.Color()
            self.assertTrue(c.enabled)

    def test_enabled_override(self):
        """Test explicit enable behavior."""
        stderr = io.StringIO()
        with contextlib.redirect_stderr(stderr):
            stderr.isatty = lambda: True
            os.environ["NOCOLOR"] = "no"
            c = rh.terminal.Color()
            self.assertTrue(c.enabled)
            c = rh.terminal.Color(False)
            self.assertFalse(c.enabled)

            stderr.isatty = lambda: False
            os.environ["NOCOLOR"] = "yes"
            c = rh.terminal.Color()
            self.assertFalse(c.enabled)
            c = rh.terminal.Color(True)
            self.assertTrue(c.enabled)

    def test_output_disabled(self):
        """Test output when coloring is disabled."""
        c = rh.terminal.Color(False)
        self.assertEqual(c.start(rh.terminal.Color.BLACK), "")
        self.assertEqual(c.color(rh.terminal.Color.BLACK, "foo"), "foo")
        self.assertEqual(c.stop(), "")

    def test_output_enabled(self):
        """Test output when coloring is enabled."""
        c = rh.terminal.Color(True)
        self.assertEqual(c.start(rh.terminal.Color.BLACK), "\x1b[1;30m")
        self.assertEqual(
            c.color(rh.terminal.Color.BLACK, "foo"), "\x1b[1;30mfoo\x1b[m"
        )
        self.assertEqual(c.stop(), "\x1b[m")


class PrintStatusLine(unittest.TestCase):
    """Verify behavior of print_status_line."""

    def test_terminal(self):
        """Check tty behavior."""
        stderr = io.StringIO()
        stderr.isatty = lambda: True
        with contextlib.redirect_stderr(stderr):
            rh.terminal.print_status_line("foo")
            rh.terminal.print_status_line("bar", print_newline=True)
        csi = rh.terminal.CSI_ERASE_LINE_AFTER
        self.assertEqual(stderr.getvalue(), f"\rfoo{csi}\rbar{csi}\n")

    def test_no_terminal(self):
        """Check tty-less behavior."""
        stderr = io.StringIO()
        with contextlib.redirect_stderr(stderr):
            rh.terminal.print_status_line("foo")
            rh.terminal.print_status_line("bar", print_newline=True)
        self.assertEqual(stderr.getvalue(), "foo\nbar\n")


@contextlib.contextmanager
def redirect_stdin(new_target):
    """Temporarily switch sys.stdin to |new_target|."""
    old = sys.stdin
    try:
        sys.stdin = new_target
        yield
    finally:
        sys.stdin = old


class StringPromptTests(unittest.TestCase):
    """Verify behavior of str_prompt."""

    def setUp(self):
        self.stdin = io.StringIO()

    def set_stdin(self, value: str) -> None:
        """Set stdin wrapper to a string."""
        self.stdin.seek(0)
        self.stdin.write(value)
        self.stdin.truncate()
        self.stdin.seek(0)

    def test_defaults(self):
        """Test default behavior."""
        stdout = io.StringIO()
        with redirect_stdin(self.stdin), contextlib.redirect_stdout(stdout):
            # Test EOF behavior.
            self.assertIsNone(rh.terminal.str_prompt("foo", ("a", "b")))

            # Test enter behavior.
            self.set_stdin("\n")
            self.assertEqual(rh.terminal.str_prompt("foo", ("a", "b")), "")

            # Lowercase inputs.
            self.set_stdin("Ok")
            self.assertEqual(rh.terminal.str_prompt("foo", ("a", "b")), "ok")

            # Don't lowercase inputs.
            self.set_stdin("Ok")
            self.assertEqual(
                rh.terminal.str_prompt("foo", ("a", "b"), lower=False), "Ok"
            )


class BooleanPromptTests(unittest.TestCase):
    """Verify behavior of boolean_prompt."""

    def setUp(self):
        self.stdin = io.StringIO()

    def set_stdin(self, value: str) -> None:
        """Set stdin wrapper to a string."""
        self.stdin.seek(0)
        self.stdin.write(value)
        self.stdin.truncate()
        self.stdin.seek(0)

    def test_defaults(self):
        """Test default behavior."""
        stdout = io.StringIO()
        with redirect_stdin(self.stdin), contextlib.redirect_stdout(stdout):
            # Default values.  Will loop to EOF when it doesn't match anything.
            for v in ("", "\n", "oops"):
                self.set_stdin(v)
                self.assertTrue(rh.terminal.boolean_prompt())

            # False values.
            for v in ("n", "N", "no", "NO"):
                self.set_stdin(v)
                self.assertFalse(rh.terminal.boolean_prompt())

            # True values.
            for v in ("y", "Y", "ye", "yes", "YES"):
                self.set_stdin(v)
                self.assertTrue(rh.terminal.boolean_prompt())


if __name__ == "__main__":
    unittest.main()
