from itertools import product
from random import random

funcs = [
    lambda x1, x2, x3, x4: not x2,
    lambda x1, x2, x3, x4: x1,
    lambda x1, x2, x3, x4: x1 ^ x4,
    lambda x1, x2, x3, x4: x3,
]

sequential_sync_funcs = [
    lambda x1, x2, x3, x4: not x2,
    lambda x1, x2, x3, x4: not x2,
    lambda x1, x2, x3, x4: (not x2) ^ x4,
    lambda x1, x2, x3, x4: (not x2) ^ x4,
]

probabilistic_funcs = [
    (1, funcs[0], None),
    (1, funcs[1], None),
    (1, funcs[2], None),
    (0.5, funcs[3], lambda x1, x2, x3, x4: not x3),
]


repressilator_funcs = [
    lambda x1, x2, x3: not x3,
    lambda x1, x2, x3: not x1,
    lambda x1, x2, x3: not x2,
]

funcs = repressilator_funcs

nodes = []


def init(initial_state):
    global nodes
    nodes = [bool(i) for i in initial_state]
    assert len(funcs) == len(nodes)


def synchronous_update():
    global nodes, funcs
    temp = nodes.copy()
    for i in range(len(funcs)):
        temp[i] = funcs[i](*nodes)
    nodes = temp


def sequential_update(order):
    global nodes, funcs
    assert len(order) == len(nodes)
    for i in order:
        nodes[i] = funcs[i](*nodes)


def probabilistic_update():
    global nodes, probabilistic_funcs
    funcs = probabilistic_funcs
    temp = nodes.copy()
    for i in range(len(funcs)):
        probability, *nodeFuncs = funcs[i]
        func = nodeFuncs[0] if probability >= random() else nodeFuncs[1]
        temp[i] = func(*nodes)
    nodes = temp


def state_to_str():
    return "".join(str(int(i)) for i in nodes)


def simulate(initial_state):
    print("press ENTER to simulate one step. press 'q' before ENTER to exit")
    init(initial_state)
    state = state_to_str()
    while input() != "q":
        update()
        print(state + " -> " + state_to_str(), end="")


def get_table():
    for i in product((0, 1), repeat=3):
        init(tuple(i))
        state = state_to_str()
        update()
        print(state + " -> " + state_to_str())


def update():
    # return synchronous_update()
    return sequential_update([0, 1, 2])
    # return probabilistic_update()
    # return block_sequential_update(...)
    # return asynchronous_deterministic_update(...)


def main():
    get_table()
    # simulate([0,0,0,0])


if __name__ == "__main__":
    main()
