#!/usr/bin/env python3
#
# Testing tool for the Delicious Disaster problem
#
# Usage:
#
#   python3 testing_tool.py -f <input file> <program invocation>
#   python3 testing_tool.py -n <number> <program invocation>
#
#
# Use the -f parameter to specify the input file, e.g. 1.in.
# The input file should contain one line with the starting size of the porridge.
#
# Alternatively, you can specify the starting size directly using the -n parameter.

# You can compile and run your solution as follows:

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

# Python:
#   python3 testing_tool.py -f 1.in python3 ./solution.py

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

# Haskell:
#   ghc solution.hs
#   python3 testing_tool.py -f 1.in ./solution


# 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 traceback

parser = argparse.ArgumentParser(description="Testing tool for problem Delicious Disaster.")
parser.add_argument(
    "-f",
    dest="inputfile",
    metavar="inputfile",
    default=None,
    type=argparse.FileType("r"),
    required=False,
    help="The input file to use.",
)
parser.add_argument(
    "-n",
    dest="number",
    metavar="number",
    default=None,
    type=int,
    required=False,
    help="The starting size of the porridge."
)
parser.add_argument("program", nargs="+", help="Invocation of your solution")

args = parser.parse_args()

with (
    subprocess.Popen(
        " ".join(args.program),
        shell=True,
        stdout=subprocess.PIPE,
        stdin=subprocess.PIPE,
        universal_newlines=True,
    ) as p,
):
    assert p.stdin is not None and p.stdout is not None
    p_in = p.stdin
    p_out = p.stdout

    def write(line: str):
        assert p.poll() is None, "Program terminated early"
        print(f"Write: {line}", flush=True)
        p_in.write(f"{line}\n")
        p_in.flush()

    def read() -> str:
        line = p_out.readline().strip()
        if line == "":
            assert p.poll() is None, "Program terminated early"
        assert line != "", "Read empty line or closed output pipe"
        print(f"Read: {line}", flush=True)
        return line

    # Parse input
    if args.inputfile is not None:
        radius = int(args.inputfile.readline())
    elif args.number is not None:
        radius = args.number
    else:
        assert False, "Must specify input file or starting size"

    # Simulate interaction
    try:
        queries = 0
        px, py = 0, 0
        while True:
            line = read().split()
            if line[0] == "?":
                queries += 1
                radius += 1
                x, y = map(int, line[1:])
                assert abs(x - px) + abs(y - py) <= 1000, "query location too far from previous location"
                write("in" if abs(x) + abs(y) <= radius else "out")
                px, py = x, y
            elif line[0] == "!":
                assert int(line[1]) == radius, f"Your solution is not correct; expected {radius}, but got {line[1]}."
                break
            else:
                assert False, "Line does not start with '?' or '!'."

        print()
        print(f"Correctly identified the current size {radius}.", flush=True)
        print(f"Queries used: {queries}", flush=True)
        assert queries <= 5000, "Used too many queries"
        assert (line := p_out.readline()) == "", f"Your submission printed extra data after finding a solution: '{line[:100].strip()}{'...' if len(line) > 100 else ''}'"
        print(f"Exit code: {p.wait()}", flush=True)
        assert p.wait() == 0, "Your submission did not exit cleanly after finishing"

    except AssertionError as e:
        print()
        print(f"Error: {e}")
        print()
        print(f"Killing your submission.", flush=True)
        p.kill()
        exit(1)

    except Exception as e:
        print()
        print("Unexpected error:")
        traceback.print_exc()
        print()
        print(f"Killing your submission.", flush=True)
        p.kill()
        exit(1)
