# coding: utf-8
import os, sys, subprocess
import time
from z3 import *

CHOICES = ["Rock", "Paper", "Scissors", "Spock", "Lizard"]

def next_random_number(s_i):
    return ((11 * s_i) + 12345) & 0x7fff

class RPSSLComputer:
    def __init__(self, s0):
        self.previous_random_number = s0

    def compute_choice(self):
        random_number = next_random_number(self.previous_random_number)
        self.previous_random_number = random_number
        return random_number % 5, CHOICES[random_number % 5]


# cf. https://bigbangtheory.fandom.com/wiki/Rock,_Paper,_Scissors,_Lizard,_Spock
def winning_mapping(i):
    if i == 0: return 1#"Paper"
    if i == 1: return 2#"Scissors"
    if i == 2: return 0#"Rock"
    if i == 3: return 4#"Lizard"
    if i == 4: return 0#"Rock"
    return "Did you forget to compute the remainder modulo 5?"

def compute_winner(computer, player):
    if computer == player:
        return "\tTie.", False
    is_player_bigger = True if player > computer else False
    absolute_difference = abs(computer - player)
    if absolute_difference % 2 == 1:
        if is_player_bigger:
            return "\tPlayer wins.", True
        else:
            return "\tComputer wins.", False
    else:
        if is_player_bigger:
            return "\tComputer wins.", False
        else:
            return "\tPlayer wins.", True

solver = Solver()
states = list()

# We are adding the first state s_0 to the list of states
states.append(BitVec("state0", 16))

s0 = int(sys.argv[1])
computer = RPSSLComputer(s0)

preprocess_count = 5

def add_constraint(solver, index, computers_choice):
    # TODO create a BitVec and append it to the list of states
    # You might want to call it state{index}

    # TODO Enforce that the newly added BitVec-variable must evaluate to the result of the LCG computation using the previous result

    # TODO Enforce that the unsigned remainder of the newly added BitVec-varialbe and 5 evaluates to the choice of the computer

    pass

def store_backtracking_point(solver):
    solver.push()

def restore_backtracking_point(solver):
    solver.pop()

def add_next_state_constraint(solver):
    s_i_plus_1 = BitVec("s_i_plus_1", 16)
    # TODO Enforce that the next state value is computed via the same computation as above

    return s_i_plus_1

def get_players_choice(solver, s_i_plus_1):
    # TODO Get the value of next_state from the model and return it modulo 5
    # Hint: winning_mapping(...) returns a good answer for the computer's choice
    # Hint: use solver.model() like a python dict.
    # Hint: use `.as_long()` to convert a z3 variable to a python integer value
    return 0


# Main loop:

# We read preprocess_count many choices from the computer before we start to ask z3 for a solution
# Note that for these preprocessing rounds we do not need to make a good guess and compute the winner
# We are only interested in what the computer picks for the first few rounds
for index in range(1,preprocess_count):
    computer_choice, _ = computer.compute_choice()
    player_choice = 0 # We always choose Rock since we cannot make good guesses in the beginning

    add_constraint(solver,index,computer_choice)
    output, won = compute_winner(computer_choice, player_choice)
    print(output)
    if won: print("Congratulations!")
    else:   print("Try again.")


# Now we start by adding a 'special' variable s_i_plus_1 and try to win
index = preprocess_count
while True:
    store_backtracking_point(solver)
    s_i_plus_1 = add_next_state_constraint(solver)
    solver.check()

    player_choice = get_players_choice(solver, s_i_plus_1)
    computer_choice, _ = computer.compute_choice()
    output, won = compute_winner(computer_choice, player_choice)

    print(output)
    if won: print("Congratulations!")
    else:   print("Try again.")

    restore_backtracking_point(solver)
    add_constraint(solver, index, computer_choice)
    # input("")
    index += 1
    if index >= 100:
        break