import pytest

from gib_lang import Machine, transpile, run
from gib_lang.machine import Instruction, MachineHeadError, InvalidInstructionError


"""
FAFAFAFA FUFUFAFA FAFAFUFU                                   #toggle
FAFAFAFA FUFUFAFA FAFAFUFU FAFAFAFA                          #move right
FUFUFAFA FAFAFUFU                                            #move left
FAFAFAFA FUFUFAFA FAFAFUFU FAFAFAFA FUFUFAFA                 #loop start
FAFAFUFU                                                     #loop end

"""
@pytest.fixture
def forward() -> Machine:
    instruction = "*>*"
    return Machine(instruction)

def backward() -> Machine:
    instruction = "*<*"
    return Machine(instruction)

def forward_check(forward: Machine) -> None:
    machine, tape = run(forward, "100")
    
    assert machine.steps == 100
    assert tape == "010"
    
    machine.restart()
    
    machine, tape = run(machine, tape)
    
    assert machine.steps == 100
    assert tape == "001"
    
def backward_check(backward: Machine) -> None:
    machine, tape = run(backward, "100")
    
    assert machine.steps == 100
    assert tape == "010"
    
    machine.restart()
    
    machine, tape = run(machine, tape)
    
    assert machine.steps == 100
    assert tape == "100"

def test_drive() -> None:
    source = "*>"*8
    machine = Machine(source)

    assert machine == source

def test_hash() -> None:
    source = "*>"*8
    machine = Machine(source)
    
    assert hash(machine) == hash(source)

def test_eq() -> None:
    source = "*>"*8
    machine = Machine(source)
    machine2 = Machine(source)
    assert machine == machine2

def test_copy() -> None:
    source = "*>"*8
    machine = Machine(source)
    machine2 = machine.copy()
    assert machine == machine2

def test_invalid_instruction() -> None:
    source = "*)"
    with pytest.raises(InvalidInstructionError):
         Machine(source)
    

def test_head() -> None:
    source = "*>"*8
    machine = Machine(source)

    assert len(machine) == 16
    assert machine.head == 0

    for i in range(15):
        assert machine.head == i
        machine.move_right()
    
    assert machine.steps == 15

    assert machine.head == 15
    with pytest.raises(MachineHeadError):
        machine.move_right()
    
    assert machine.head == 15
    assert machine.steps == 16

    for i in range(15, 0, -1):
        assert machine.head == i
        machine.move_left()
    assert machine.head == 0
    assert machine.steps == 31
    with pytest.raises(MachineHeadError):
        machine.move_left()
    assert machine.head == 0
    assert machine.steps == 32

def test_restart() -> None:
    source = "*>"*8
    machine = Machine(source)

    machine.move_right(8)
    
    assert machine.head == 8
    assert machine.steps == 8

    machine.restart()

    assert machine.head == 0
    assert machine.steps == 0

def test_getitem() -> None:
    source = "*>"*8
    machine = Machine(source)

    assert machine[0] == Instruction.TOGGLE
    assert machine[1] == Instruction.HEAD_RIGHT
    assert machine[2] == Instruction.TOGGLE

def test_instruction_prop() -> None:
    source = "*>"*8
    machine = Machine(source)

    assert machine.instruction == Instruction.TOGGLE
    machine.move_right()
    assert machine.instruction == Instruction.HEAD_RIGHT
    machine.move_right()
    assert machine.instruction == Instruction.TOGGLE

#TODO: loop tests