"""Tests for random exercises"""
import unittest
from unittest.mock import patch

from helpers import run_program, ModuleTestCase, make_file, capture_stdin


class RollTests(ModuleTestCase):

    """
    Tests for roll.

    Simulate rolling one or more dice.  Accept the number of sides for
    each die as input and print the sum of the rolled faces as output.
    """

    module_path = "roll.py"

    def roll_repeatedly(self, args, times):
        outputs = (run_program("roll.py", args) for _ in range(times))
        rolls = {int(o.strip()) for o in outputs}
        return rolls

    def test_no_arguments(self):
        rolls = self.roll_repeatedly([], times=200)
        self.assertEqual(rolls, set(range(1, 7)))

    def test_with_one_argument(self):
        rolls = self.roll_repeatedly(["20"], times=300)
        self.assertEqual(rolls, set(range(1, 21)))

    def test_with_two_arguments(self):
        rolls = self.roll_repeatedly(["6", "20"], times=1000)
        self.assertEqual(rolls, set(range(2, 27)))

    def test_invalid_argument(self):
        with self.assertRaises(BaseException):
            run_program("roll.py", ["4.5"])


class RandLineTests(ModuleTestCase):

    """
    Tests for randline.py.

    Take file or standard input and print a random line from the stream.
    """

    module_path = "randline.py"

    def get_random_lines(self, args, times, stdin=""):
        outputs = set()
        for _ in range(times):
            with capture_stdin(stdin):
                outputs.add(run_program("randline.py", args))
        return outputs

    def test_with_one_argument(self):
        lines = ["line 1", "line 2", "line 3", "line 4"]
        expected = {line + "\n" for line in lines}
        with make_file("\n".join(lines)) as filename:
            outputs = self.get_random_lines([filename], times=100)
        self.assertEqual(outputs, expected)

    @unittest.skip("Comment out this line to test multiple arguments")
    def test_multiple_arguments(self):
        with make_file("line 1\n") as file1, make_file("line 2\n") as file2:
            outputs = self.get_random_lines([file1, file2], times=10)
        self.assertEqual(outputs, {"line 1\n", "line 2\n"})

    def test_with_stdin(self):
        lines = ["line 1", "line 2", "line 3", "line 4"]
        expected = {line + "\n" for line in lines}
        outputs = self.get_random_lines([], times=100, stdin="\n".join(lines))
        self.assertEqual(outputs, expected)


class ShuffleTests(ModuleTestCase):

    """
    Tests for shuffle.py

    Rearrange lines in the input file and write them to the output file.
    """

    module_path = "shuffle.py"

    def shuffle(self, in_file, out_file, times):
        outputs = set()
        for _ in range(times):
            run_program("shuffle.py", [in_file, out_file])
            with open(out_file) as f:
                outputs.add(f.read())
        return outputs

    def test_one_line(self):
        with make_file("hello") as in_file, make_file() as out_file:
            outputs = self.shuffle(in_file, out_file, times=1)
        self.assertEqual(outputs, {"hello\n"})

    def test_multiple_lines(self):
        lines = ["1", "2", "3"]
        expected = {
            "1\n2\n3\n",
            "1\n3\n2\n",
            "2\n1\n3\n",
            "2\n3\n1\n",
            "3\n1\n2\n",
            "3\n2\n1\n",
        }
        with make_file("\n".join(lines)) as in_file, make_file() as out_file:
            outputs = self.shuffle(in_file, out_file, times=50)
        self.assertEqual(outputs, expected)

    def test_one_argument(self):
        with make_file("hello") as in_file:
            with self.assertRaises(Exception):
                run_program("shuffle.py", [in_file])

    def test_no_arguments(self):
        with self.assertRaises(Exception):
            run_program("shuffle.py", [])

    def test_non_file_arguments(self):
        with self.assertRaises(Exception):
            run_program("shuffle.py", ["http://trey.in", "http://example.com"])


class CapitalGuessing(ModuleTestCase):

    """
    Tests for guess_capitals.py

    Prompt user to guess capitals.
    """

    module_path = "guess_capitals.py"

    def test_single_capital_correct_guess(self):
        calls = ["Capital"]
        expected = "Correct! Capital is the capital of Place\n"
        with patch("builtins.input", side_effect=calls) as input:
            with make_file("state,capital\nPlace,Capital") as filename:
                output = run_program("guess_capitals.py", [filename])
        input.assert_called_once_with("What is the capital of Place? ")
        self.assertEqual(output, expected)

    def test_single_capital_incorrect_guess(self):
        calls = ["Wrong", "Capital"]
        expected = (
            "That's not correct.  Try again.\n"
            "Correct! Capital is the capital of Place\n"
        )
        with patch("builtins.input", side_effect=calls) as input:
            with make_file("state,capital\nPlace,Capital") as filename:
                output = run_program("guess_capitals.py", [filename])
        input.assert_called_with("What is the capital of Place? ")
        self.assertEqual(output, expected)

    def test_multiple_capitals(self):
        capital_data = "state,capital\nPlace1,Capital1\nPlace2,Capital2"
        calls = ["Capital1", "Capital2"] * 50
        not_correct = "That's not correct.  Try again.\n"
        correct1 = "Correct! Capital1 is the capital of Place1\n"
        correct2 = "Correct! Capital2 is the capital of Place2\n"
        expected = {
            not_correct + correct1,
            not_correct + correct2,
            correct1,
            correct2,
        }
        with patch("builtins.input", side_effect=calls) as input:
            with make_file(capital_data) as filename:
                outputs = {
                    run_program("guess_capitals.py", [filename]) for _ in range(50)
                }
        input.assert_any_call("What is the capital of Place1? ")
        input.assert_any_call("What is the capital of Place2? ")
        self.assertEqual(outputs, expected)


if __name__ == "__main__":
    from helpers import error_message

    error_message()
