#!/usr/bin/env python3
#
# Testing tool for the Journey to Mastery problem
#
# Usage:
#
#   python3 testing_tool.py -f inputfile <program invocation>
#
#
# Use the -f parameter to specify the input file, e.g. 1.in.
# The input file should contain two lines containing:
# - an integer between 1 and 100 inclusive: the initial distance between your character and the training dummy.
# - a list of actions to be performed by the training dummy.
#
#
# You can compile and run your solution as follows:

# C++:
#   g++ solution.cpp
#   python3 testing_tool.py -f 1.in ./a.out

# Java
#   javac solution.java
#   python3 testing_tool.py -f 1.in java solution

# Python3
#   python3 testing_tool.py -f 1.in python3 ./solution.py


# The tool is provided as-is, and you should feel free to make
# whatever alterations or augmentations you like to it.
#
# The tool attempts to detect and report common errors, but it is not an
# exhaustive test. It is not guaranteed that a program that passes this testing
# tool will be accepted.


import argparse
import subprocess
import sys
import traceback


def write(p, line):
    assert p.poll() is None, 'Program terminated early'
    print('Write: {}'.format(line), flush=True)
    p.stdin.write('{}\n'.format(line))
    p.stdin.flush()


def read(p):
    assert p.poll() is None, 'Program terminated early'
    line = p.stdout.readline().strip()
    assert line != '', 'Read empty line or closed output pipe'
    print('Read: {}'.format(line), flush=True)
    return line


parser = argparse.ArgumentParser(description='Testing tool for problem Journey to Mastery.')
parser.add_argument('-f', dest='inputfile', metavar='inputfile', default=None, type=argparse.FileType('r'),
                    required=True, help='The input file to use.')
parser.add_argument('program', nargs='+', help='Invocation of your solution')

args = parser.parse_args()

distance = None
dummy_actions = None
with args.inputfile as f:
    lines = f.readlines()
    assert len(lines) > 0
    distance = int(lines[0])
    dummy_actions = list(lines[1])
    assert 1 <= distance <= 100

assert distance is not None
assert dummy_actions is not None

with subprocess.Popen(' '.join(args.program), shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE,
                      universal_newlines=True) as p:
    try:
        write(p, distance)

        time = 0

        player_cooldown = 0
        player_hadoukens = []
        player_kicking = False
        player_shoryuken = False

        dummy_cooldown = False
        dummy_in_air = False
        dummy_hadoukens = []
        dummy_kicking = False

        while True:
            if player_kicking and distance <= 2 or player_shoryuken and distance <= 1:
                write(p, "V")
                break

            if dummy_hadoukens and time == dummy_hadoukens[0]:
                if player_shoryuken:
                    dummy_hadoukens.pop(0)
                else:
                    raise Exception('Failed: you got hit by a Hadouken!')

            dummy_in_air = False
            dummy_kicking = False

            if dummy_cooldown:
                write(p, "-")
                dummy_cooldown = False
            else:
                dummy_action = dummy_actions.pop(0) if dummy_actions else "J"
                write(p, dummy_action)
                if dummy_action == "W":
                    distance = max(1, distance - 1)
                elif dummy_action == "J":
                    distance = max(1, distance - 2)
                    dummy_in_air = True
                    dummy_kicking = True
                elif dummy_action == "K":
                    dummy_kicking = True
                    dummy_cooldown = True
                elif dummy_action == "H":
                    if player_hadoukens:
                        player_hadoukens.pop(0)
                    else:
                        dummy_hadoukens.append(time + distance)
                    dummy_cooldown = True
                else:
                    raise Exception("Dummy performed invalid action {}".format(dummy_action))

            if player_hadoukens and time - player_hadoukens[0] == distance:
                if dummy_in_air:
                    player_hadoukens.pop(0)
                else:
                    write(p, "V")
                    break

            time += 1

            if player_hadoukens and time - player_hadoukens[0] == distance:
                if dummy_in_air:
                    player_hadoukens.pop(0)
                else:
                    write(p, "V")
                    break

            player_kicking = False
            player_shoryuken = False

            if player_cooldown > 0:
                dash = read(p)
                if dash != "-":
                    raise Exception("Player did not output '-' while move was on cooldown")
                player_cooldown -= 1
            else:
                player_action = read(p)
                if player_action == "K":
                    player_kicking = True
                    player_cooldown = 1
                elif player_action == "S":
                    player_shoryuken = True
                    player_cooldown = 2
                elif player_action == "H":
                    if dummy_hadoukens:
                        dummy_hadoukens.pop(0)
                    else:
                        player_hadoukens.append(time)
                    player_cooldown = 1
                elif player_action != "N":
                    raise Exception("Player performed invalid action {}".format(player_action))

            if dummy_hadoukens and time == dummy_hadoukens[0]:
                if player_shoryuken:
                    dummy_hadoukens.pop(0)
                else:
                    raise Exception('Failed: you got hit by a Hadouken!')

            if dummy_kicking and distance <= (2 - dummy_in_air) and not player_shoryuken:
                raise Exception('Failed: you got hit by a {}!'.format("Jump Kick" if dummy_in_air else "Ground Kick"))

            time += 1

        assert p.wait() == 0, 'Your submission did not exit cleanly after finishing.'

        sys.stdout.write('\nSuccess.\nTraining mode lasted {} units of time.\n'.format(time))
    except:
        print()
        traceback.print_exc()
        print()
        try:
            p.wait(timeout=2)
        except subprocess.TimeoutExpired:
            print('Killing your submission after 2 second timeout.')
            p.kill()
    finally:
        sys.stdout.flush()
        sys.stderr.flush()
        sys.stdout.write('Exit code: {}\n'.format(p.wait()))
        sys.stdout.flush()
