"""
The terminal prompts.

These are the places where a wrong answer is silently accepted, which is worse
than a rejected one: the plot still appears, and it is not the plot that was
asked for.
"""

import builtins
import sys

import pytest

from dispnc.figure import is_video, still_path
from dispnc.interactive import ask, prompt_choice, resolve_choice
from dispnc.selection import AVERAGE_WORDS, EVERY_WORDS


@pytest.fixture
def terminal(monkeypatch):
    """
    Pretend stdin is a terminal, and answer prompts from a scripted queue.
    """
    def script(answers):
        queue = list(answers)

        def fake_input(_prompt=''):
            if not queue:
                raise EOFError
            return queue.pop(0)

        monkeypatch.setattr(builtins, 'input', fake_input)
        monkeypatch.setattr(sys.stdin, 'isatty', lambda: True)
        return queue

    return script


@pytest.mark.parametrize('answer', ['y', 'Y', 'yes', 'YES', 'Yes', 'oui', 'true', '1'])
def test_every_way_of_saying_yes(terminal, answer):
    terminal([answer])
    assert ask("Display?", interactive=True) is True


@pytest.mark.parametrize('answer', ['n', 'no', 'NO', 'non', 'false', '0'])
def test_every_way_of_saying_no(terminal, answer):
    terminal([answer])
    assert ask("Display?", interactive=True, default=True) is False


def test_enter_takes_the_default(terminal):
    terminal([''])
    assert ask("Display?", interactive=True, default=True) is True


def test_an_unrecognized_answer_asks_again(terminal):
    """
    Silently reading 'maybe' as 'no' is what made a typed 'yes' fail to open the
    view the user asked for.
    """
    terminal(['maybe', 'yes'])
    assert ask("Display?", interactive=True) is True


def test_a_batch_run_never_blocks(monkeypatch):
    monkeypatch.setattr(sys.stdin, 'isatty', lambda: False)

    def explode(_prompt=''):
        raise AssertionError("a non-interactive run must not read stdin")

    monkeypatch.setattr(builtins, 'input', explode)
    assert ask("Display?", interactive=True, default=True) is True
    assert ask("Display?", interactive=False) is False


class FakeSource:
    """
    The little of a source the variable loop touches.
    """
    def __init__(self, *names):
        self.variables = list(names)
        self.closed = False

    def __contains__(self, name):
        return name in self.variables

    def dims_and_shape(self, _name):
        return ('lat', 'lon'), (3, 4)

    def close(self):
        self.closed = True


@pytest.fixture
def loop(monkeypatch, terminal):
    """
    Run the variable loop over a fake file, drawing one figure per variable.
    """
    import matplotlib
    matplotlib.use('Agg')
    import matplotlib.pyplot as plt

    from dispnc import cli

    def draw(*_args, **_kwargs):
        plt.figure()
        return 0

    def run(answers, names=('tsurf', 'ps')):
        src = FakeSource(*names)
        terminal(answers)
        monkeypatch.setattr(cli.os.path, 'isfile', lambda _p: True)
        monkeypatch.setattr(cli, '_open_normalized', lambda _p: (src, None))
        monkeypatch.setattr(cli, 'get_dimension_indices', lambda *a, **k: {})
        monkeypatch.setattr(cli, 'plot_variable', draw)
        plt.close('all')
        status = cli.visualize_variable_interactive('file.nc')
        return status, plt.get_fignums()

    was_interactive = plt.isinteractive()
    warn_at = plt.rcParams['figure.max_open_warning']
    yield run
    plt.close('all')
    plt.rcParams['figure.max_open_warning'] = warn_at
    plt.ion() if was_interactive else plt.ioff()


def test_each_variable_leaves_its_figure_on_screen(loop):
    """
    The loop used to close every window before drawing the next one, so two
    variables could never be compared. Keeping them is the whole point of
    plotting a second one.
    """
    status, open_figures = loop(['tsurf', 'ps', ''])
    assert status == 0
    assert len(open_figures) == 2


def test_close_clears_the_screen(loop):
    """
    The counterpart to keeping them: with no automatic close, there has to be a
    deliberate one.
    """
    status, open_figures = loop(['tsurf', 'ps', 'close', ''])
    assert status == 0
    assert open_figures == []


def test_a_variable_really_called_close_is_still_plotted(loop):
    """
    The command is guarded on the file rather than on the word, so a file that
    holds a variable named 'close' can still plot it.
    """
    _status, open_figures = loop(['close', ''], names=('close', 'tsurf'))
    assert len(open_figures) == 1


DIMS = ['lon', 'lat']
ALIASES = {'longitude': 'lon', 'latitude': 'lat', 'Longitude': 'lon'}


@pytest.mark.parametrize('answer, expected', [
    ('lon', 'lon'),                # exact
    ('LAT', 'lat'),                # case
    ('longitude', 'lon'),          # the coordinate name, not the dimension
    ('Latitude', 'lat'),
    ('lo', 'lon'),                 # unique prefix
])
def test_the_x_axis_answer_resolves_to_a_dimension(answer, expected):
    """
    The prompt validates dimension names ('lon') while the file advertises
    coordinate names ('longitude'). Both have to land on the same axis, or a
    reasonable answer is discarded and the default plotted instead.
    """
    assert resolve_choice(answer, DIMS, ALIASES) == expected


@pytest.mark.parametrize('answer', ['depth', 'l', ''])
def test_an_ambiguous_or_unknown_answer_resolves_to_nothing(answer):
    # 'l' is a prefix of both lon and lat, so it must not pick one at random
    assert resolve_choice(answer, DIMS, ALIASES) is None


def test_prompt_restores_the_completer_it_replaced(terminal):
    """
    The interactive loop installs a variable-name completer. Leaving a
    dimension-name one behind would offer the wrong words at the next prompt -
    which is the same class of bug, one prompt later.
    """
    import readline

    def sentinel(text, state):
        return None

    readline.set_completer(sentinel)
    terminal(['lat'])

    assert prompt_choice("Which dimension on X?", DIMS, ALIASES, default='lon') == 'lat'
    assert readline.get_completer() is sentinel


def test_prompt_keeps_the_default_on_enter(terminal):
    terminal([''])
    assert prompt_choice("Which dimension on X?", DIMS, default='lon') == 'lon'


def test_prompt_rejects_then_accepts(terminal):
    terminal(['nonsense', 'lat'])
    assert prompt_choice("Which dimension on X?", DIMS, default='lon') == 'lat'


def test_the_dimension_prompt_takes_words_as_well_as_letters():
    """
    'a' and 'e' are shortcuts for averaging and for keeping every value; the
    words they stand for have to work too.
    """
    assert {'a', 'avg', 'average', 'mean'} <= AVERAGE_WORDS
    assert {'', 'e', 'all', 'every'} <= EVERY_WORDS
    assert not (AVERAGE_WORDS & EVERY_WORDS)


@pytest.mark.parametrize('path, video', [
    ('out.mp4', True), ('out.GIF', True), ('out.webm', True),
    ('out.png', False), ('out.pdf', False), (None, False), ('', False),
])
def test_video_extensions_are_recognised(path, video):
    assert is_video(path) is video


def test_a_figure_that_cannot_animate_falls_back_to_a_still():
    assert still_path('movie.mp4') == 'movie.png'
    assert still_path('fig.pdf') == 'fig.pdf'
