# -*- coding: utf-8 -*-
"""Play one game through a driver (session.py) like a player would, recording every step.

Answer policies:
  correct          always right (the whole game, 9 rounds)
  fail-at-N        right until round N, wrong there
  random           a random remaining answer every round
  script:ABDC...   the letters to press, round by round (wrong ones end the game)
Lifelines: {"5050": 3, "tip": 5, "audience": 7} -> used in that round before answering.
"""
import random
import time

LETTERS = 'ABCD'
LIFE_INTENTS = {'5050': 'life5050', 'tip': 'lifetip', 'audience': 'lifeaudience'}
LIFE_FLAGS = {'5050': '5050', 'tip': 'tip', 'audience': 'audience'}
CLASSIC_INTENTS = {'start': 'primary', 'next': 'primary', 'results': 'primary', 'rules': 'secondary'}


def parse_lifelines(spec):
    """'5050:3,tip:5' -> {'5050': 3, 'tip': 5}."""
    result = {}
    for part in filter(None, (spec or '').split(',')):
        name, _, round_num = part.partition(':')
        name = {'poll': 'audience', '50:50': '5050'}.get(name.strip().lower(), name.strip().lower())
        if name not in LIFE_INTENTS or not round_num.strip().isdigit():
            raise SystemExit('--lifelines: use 5050:N,tip:N,audience:N ({!r})'.format(part))
        result[name] = int(round_num)
    return result


class Recorder:
    def __init__(self, driver, on_step=None):
        self.driver = driver
        self.steps = []
        self.on_step = on_step

    def step(self, name, action=None, screens=None, extra=None, timeout=180.0, input_mode='act'):
        started = time.time()
        before = self.driver.snapshot()
        if before.get('design') == 'classic':
            action = CLASSIC_INTENTS.get(action, action)
        if action:
            if action == 'back':
                self.driver.back()
            elif input_mode == 'click':
                self.driver.click(action)
            else:
                self.driver.act(action)
        snap = self.driver.wait_settled(screens, timeout=timeout, extra=extra)
        record = {'step': name, 'action': action, 'from': before.get('screen'), 'screen': snap.get('screen'),
                  'game_state': snap.get('game_state'), 'round': snap.get('round'),
                  'wait_ms': int((time.time() - started) * 1000), 'snapshot': snap}
        self.steps.append(record)
        if self.on_step:
            self.on_step(record)
        return snap


def choose(snap, policy, rng):
    """Index of the answer to press in this round."""
    question = snap.get('question') or {}
    correct = question.get('correct', 0)
    removed = set(snap.get('removed') or [])
    remaining = [i for i in range(4) if i not in removed]
    wrong = [i for i in remaining if i != correct] or [i for i in range(4) if i != correct]
    round_num = snap.get('round', 1)
    if policy == 'correct':
        return correct
    if policy.startswith('fail-at-'):
        return rng.choice(wrong) if round_num == int(policy[8:]) else correct
    if policy == 'random':
        return rng.choice(remaining)
    if policy.startswith('script:'):
        letters = policy[7:].upper()
        letter = letters[round_num - 1] if round_num <= len(letters) else LETTERS[correct]
        return LETTERS.index(letter)
    raise SystemExit('unknown answer policy: ' + policy)


def play(driver, policy='correct', lifelines=None, rules=False, seed=None, timeout=180.0, input_mode='act',
         on_step=None, max_rounds=9):
    """Title -> intro -> rounds -> end screen. Returns (steps, outcome); a timeout or failure ends the game early."""
    rec = Recorder(driver, on_step)
    try:
        return rec.steps, _play(rec, policy, lifelines, rules, seed, timeout, input_mode, max_rounds)
    except Exception as exc:
        import traceback
        rec.steps.append({'step': 'abort', 'action': None, 'screen': None, 'wait_ms': 0,
                          'error': '{}: {}'.format(type(exc).__name__, exc), 'traceback': traceback.format_exc(),
                          'snapshot': _safe_snapshot(driver)})
        if on_step:
            on_step(rec.steps[-1])
        return rec.steps, 'aborted'


def _safe_snapshot(driver):
    try:
        return driver.snapshot()
    except Exception:
        return {}


def _play(rec, policy, lifelines, rules, seed, timeout, input_mode, max_rounds):
    rng = random.Random(seed)
    lifelines = lifelines or {}
    run = lambda *a, **k: rec.step(*a, timeout=timeout, input_mode=input_mode, **k)   # noqa: E731

    snap = run('title', screens={'title'})
    if rules:
        run('rules', 'rules', screens={'rules'})
        run('rules-back', 'back', screens={'title'})
    snap = run('start', 'start', screens={'intro', 'error'})
    if snap.get('screen') == 'error':
        return 'error'
    snap = run('question-1', 'start', screens={'question', 'error'})
    outcome = 'error'
    while snap.get('screen') == 'question':
        round_num = snap.get('round')
        for name, at in sorted(lifelines.items(), key=lambda item: item[0]):
            if at == round_num and not (snap.get('lifelines') or {}).get(LIFE_FLAGS[name]):
                snap = run('life-{}-{}'.format(name, round_num), LIFE_INTENTS[name], screens={'question'},
                           extra=lambda s, n=name: (s.get('lifelines') or {}).get(LIFE_FLAGS[n]))
        index = choose(snap, policy, rng)
        snap = run('answer-{}'.format(round_num), 'answer{}'.format(index),
                   screens={'feedback', 'gameover', 'win', 'error'})
        screen = snap.get('screen')
        if screen == 'gameover':
            outcome = 'lost-at-{}'.format(round_num)
            break
        if screen == 'win':
            snap = run('results', 'results', screens={'results'})
            outcome = 'won'
            break
        if screen != 'feedback' or round_num >= max_rounds:
            outcome = 'stopped-at-{}'.format(round_num) if screen == 'feedback' else 'error'
            break
        snap = run('question-{}'.format(round_num + 1), 'next', screens={'question', 'error'})
    return outcome
