import { ExecutionState, ExecutionStateActionTypes, START_EXECUTING, PAUSE_EXECUTION, STEP_EXECUTION, startExecutionAction, TOGGLE_SPEED, TOGGLE_AUTOSTEP, RESET_EXECUTION, resetExecutionAction, WON_LEVEL } from "./types";
import { cloneDeep, pullAt } from "lodash"

const INIT_STATE: ExecutionState = {
    running: false,
    autostep: false,
    won: false,
    instructionPtr: { fn: "main", fnIdx: 0 },
    callStack: [],
    fast: false,
    finished: false,
    playerPos: { voxelIdx: 0, direction: "west" },
    code: {}
}

export function executionStateReducer(state: ExecutionState = INIT_STATE, action: ExecutionStateActionTypes) {
    switch (action.type) {
        case START_EXECUTING:
            return reduceStart(state, action)
        case PAUSE_EXECUTION:
            return reducePause(state)
        case STEP_EXECUTION:
            return reduceStep(state)
        case TOGGLE_SPEED:
            return reduceToggleSpeed(state)
        case TOGGLE_AUTOSTEP:
            return reduceToggleAutoStep(state)
        case RESET_EXECUTION:
            return reduceReset(state)
        case WON_LEVEL:
            return reduceWonLevel(state)
        default:
            return state
    }
}

const reduceReset = (state: ExecutionState) => {
    return {
        ...state,
        won: false,
        running: false,
        finished: false,
        instructionPtr: { fn: "main", fnIdx: 0 },
        callStack: []
    }
}

const reduceStart = (state: ExecutionState, action: startExecutionAction) => {
    let code = {}
    for (let i = 0; i < action.level.codeviews.length; i++) {
        code[action.level.codeviews[i].name] = action.code[i]
    }
    return {
        ...INIT_STATE,
        won: false,
        running: true,
        playerPos: action.playerPos,
        code: code,
        // level: cloneDeep(action.level)
    }
}

const reducePause = (state: ExecutionState) => {
    return { ...state, running: !state.running }
}

const reduceToggleAutoStep = (state: ExecutionState) => {
    return { ...state, autostep: true }
}

const reduceStep = (state: ExecutionState) => {
    // here the machine makes one single instructoin.
    // it reads the instruction from the code variable at the
    // instruction pointer indices, executes the instruction
    // and changes the instructoin pointers.
    let newState = cloneDeep(state)
    while (newState.instructionPtr.fnIdx >= newState.code[newState.instructionPtr.fn]['blocks'].length) {
        let newPtr = newState.callStack.pop()
        if (!newPtr) {
            newState.running = false
            newState.finished = true
            return newState
        }
        newState.instructionPtr = newPtr
    }

    let instruction = newState.code[newState.instructionPtr.fn]['blocks'][newState.instructionPtr.fnIdx].name

    while (instruction in newState.code) { // set instruction pointer to fn and put returnaddr on stack
        let returnAddr = { fn: newState.instructionPtr.fn, fnIdx: newState.instructionPtr.fnIdx + 1 }
        newState.instructionPtr.fn = instruction
        newState.instructionPtr.fnIdx = 0
        newState.callStack.push(returnAddr)
        instruction = newState.code[newState.instructionPtr.fn]['blocks'][newState.instructionPtr.fnIdx].name
    }

    // now check if the istruction is a valid command
    newState.instruction = undefined
    if (instruction in instructions)
        newState.instruction = instruction
    newState.instructionPtr.fnIdx++
    return newState
}

const reduceToggleSpeed = (state: ExecutionState) => {
    return { ...state, fast: !state.fast }
}

const reduceWonLevel = (state: ExecutionState) => {
    return { ...state, won: true }
}

const instructions = {
    "forward": (state: ExecutionState) => { },
    "turn_right": (state: ExecutionState) => { },
    "turn_left": (state: ExecutionState) => { },
    "action": (state: ExecutionState) => { },
    "jump": (state: ExecutionState) => { }
}
