let opMaxTime: number
let procMaxTime: number
let procsToDo: number
let recoveryTriesMax: number
let tenseTime: number
let flatlineTimeMax: number
let recoveryProbability: number
let aWasPressed: boolean
let bWasPressed: boolean
let digitsImg: Image
let tweezerCount_: number
let wasTweezers: boolean
let wasNose: boolean
let isTweezers: boolean

// P0 blue wire, tweezers (active high)
// P1, green wire nose button/LED (active high)
// P2/red speaker (not a self toned beeper, but a piezo or a speaker)
opMaxTime = 120 * 1000
procsToDo = 3
procMaxTime = 60 * 1000
tenseTime = 4000
flatlineTimeMax = 15 * 1000
recoveryTriesMax = 3
recoveryProbability = 60
let highScore = 0
digitsImg = images.createImage(`
    . . # . .   . . # . .   . # # . .   . # # . .   . # . . .   . # # # .   . . # # .   . # # # .   . . # . .   . . # . .
    . # . # .   . # # . .   . . . # .   . . . # .   . # . . .   . # . . .   . # . . .   . . . . .   . # . # .   . # . # .
    . # . # .   . . # . .   . . # . .   . . # . .   . # # # .   . . # . .   . # # . .   . . # . .   . . # . .   . . # # .
    . # . # .   . . # . .   . # . . .   . . . # .   . . # . .   . . . # .   . # . # .   . # . . .   . # . # .   . . . # .
    . . # . .   . # # # .   . # # # .   . # # . .   . . # . .   . # # . .   . . # . .   . # . . .   . . # . .   . # # . .
    `)
startIOMonitor()
while (true) {
    splashScreen()
    if (buttonB()) {
        basic.showAnimation(`
            . . . . .   # # . . .   . # . . .   . # . . .   . . . # .   . . . . .
            . . . . #   . . . . #   # # . . #   . # . . #   . . # . #   . . . . #
            . . . . #   . . . . #   . . . . #   # # . . #   # # . . #   # # # # #
            # # # # #   # # # # #   # # # # #   # # # # #   # # # # #   # # # # #
            # . . . #   # . . . #   # . . . #   # . . . #   # . . . #   # . . . #
            `, 400)
        let finalScore = surgery()
        if (game.score() > highScore) {
            basic.showString("HI", 150)
            highScore = finalScore
        }
        flashDigit(finalScore)
        waitButtonB()
    } else if (buttonA()) {
        testMode()
    } else {
        basic.pause(100)
    }
}

function surgery(): number {
    let score = 0
    let speed = 150
    let opStartTime = input.runningTime()
    let procStartTime = -1
    let procNumber = -1
    let procsDone = 0
    let recoveryTry = 0
    let timer = 0
    let state = "CHOOSE PROC"
    resetButtons()
    while (true) {
        basic.pause(10)
        let event = getEvent(state)
        if (event == "CUT") {
            state = "ARREST"
        }
        // CHECK TIMERS
        if (!(procStartTime == -1)) {
            if (input.runningTime() - procStartTime > procMaxTime) {
                state = "LOST"
            } else {
                // TODO add code here to speed up near end of proc
            }
        }
        if (input.runningTime() - opStartTime > opMaxTime) {
            state = "LOST"
        } else {
            // TODO add code here to speed up near end of op
        }
        // PROCESS SPECIFIC STATES
        if (state == "CHOOSE PROC") {
            if (procNumber == -1) {
                procNumber = 1
                showDigit(procNumber)
            } else if (event == "SCROLL") {
                procNumber = procNumber + 1
                if (procNumber > 9) {
                    procNumber = 1
                }
                showDigit(procNumber)
            } else if (event == "SELECT") {
                procStartTime = input.runningTime()
                state = "HEALTHY"
                speed = 100
            }
        } else if (state == "HEALTHY") {
            speed = 100
            ecg(speed)
            if (event == "TWINGE") {
                state = "TENSE"
                timer = input.runningTime()
            } else if (event == "DONE") {
                state = "PROC DONE"
            }
        } else if (state == "TENSE") {
            speed = 25
            ecg(speed)
            if (event == "TWINGE") {
                state = "ARREST"
            } else if (input.runningTime() - timer > tenseTime) {
                state = "HEALTHY"
            }
        } else if (state == "ARREST") {
            timer = input.runningTime()
            recoveryTry = recoveryTriesMax
            state = "FLATLINE"
        } else if (state == "FLATLINE") {
            basic.showLeds(`
                . . . . .
                . # . # .
                . . . . .
                . . . . .
                # # # # #
                `)
            beepOn()
            if (event == "SHOCK") {
                state = "SHOCKING"
            } else if (input.runningTime() - timer > flatlineTimeMax) {
                state = "LOST"
            }
        } else if (state == "SHOCKING") {
            charging()
            basic.showAnimation(`
                . . . . #   . . . . #   . . . . .   . . . . .
                . . . # .   . . . # .   . . . # .   . . . . .
                . . . . .   . . # . .   . . # . .   . . # . .
                . . . . .   . . . . .   . # . . .   . # . . .
                . . . . .   . . . . .   . . . . .   . # . . .
                `, 150)
            beepNTimesFor(15, 500)
            basic.showAnimation(`
                . . . . .   . . . . .   . . . . .   # . . . #
                . . . . .   . . . . .   # . . # .   . . . . .
                . . . . .   # . # . .   . . . . .   . . . . .
                . # . . .   . # . . .   . . . . .   . . . . .
                . # . . .   . # . . .   . # . . .   . # . . .
                `, 150)
            state = "SHOCKED"
        } else if (state == "SHOCKED") {
            let recover = Math.randomInt(100)
            if (recover >= recoveryProbability) {
                state = "RECOVERED"
            } else {
                state = "FAILED"
            }
        } else if (state == "RECOVERED") {
            beepOff()
            basic.pause(500)
            beep()
            basic.showAnimation(`
                . . . . .   . . . . .   . . . . .   . . . . .   . . . . .
                . # . # .   . . . . .   . # . # .   . . . . .   . # . # .
                . . . . .   . . . . .   . . . . .   . . . . .   . . . . .
                # . . . #   . . . . .   # . . . #   . . . . .   # . . . #
                . # # # .   . . . . .   . # # # .   . . . . .   . # # # .
                `, 400)
            state = "HEALTHY"
        } else if (state == "FAILED") {
            recoveryTry = recoveryTry - 1
            if (recoveryTry > 0) {
                state = "FLATLINE"
            } else {
                state = "LOST"
            }
        } else if (state == "PROC DONE") {
            basic.showAnimation(`
                . . . . .   . . . . .   . . . . .   . . . . .   . . . . .
                . . . . .   . . . . .   . . . . .   . . . . .   . . . . #
                . . . . .   . . . . .   . . . . .   . . . # .   . . . # .
                # . . . .   # . . . .   # . # . .   # . # . .   # . # . .
                . . . . .   . # . . .   . # . . .   . # . . .   . # . . .
                `, 400)
            score = score + pointsForProc(procNumber)
            procsDone = procsDone + 1
            procStartTime = -1
            if (procsDone == procsToDo) {
                state = "OP DONE"
            } else {
                procNumber = -1
                state = "CHOOSE PROC"
            }
        } else if (state == "OP DONE") {
            basic.showAnimation(`
                . . . . .   . . . # .   . # . . .   . . # . .   . . . # .   . . . . #   . . # . .   . . # . .   . . . . .   . . . . .   . . . . .
                . . . . #   . . # . #   . # . . #   . . # . .   . . . # .   . . . . #   . . # . .   . . # . .   . # . . .   # . . . .   . . . . .
                # # # # #   # # . . #   # # . . #   . # # . .   . . # # .   . . . # #   . # # . .   . . # . .   . # . . .   # . . . .   . . . . .
                # # # # #   # # # # #   # # # # #   . # # # #   . . # # #   . . . # #   . . . # #   . . # # #   . # . # #   # . . # #   . . . # #
                # . . . #   # . . . #   # . . . #   . # . . .   . . # . .   . . . # .   . . . # .   . . . # .   . # . # .   # . . # .   . . . # .
                `, 400)
            return score
        } else if (state == "LOST") {
            beepOn()
            basic.showLeds(`
                . # . # .
                . . # . .
                . # . # .
                . . . . .
                # # # # #
                `)
            basic.pause(3000)
            beepOff()
            basic.showAnimation(`
                . . . . .   . . . . .   # # # # .   . . . . .   . . . . .   . . . . .   . . . . .   . . . . .   . . . . .
                . . . . #   # # # # #   . . . . #   . . . . #   . . . . #   # . # . #   . . . . #   # . # . #   . . . . #
                # # # # #   . . . . #   . . . . #   . . . . #   . . . . #   . . . . #   . . . . #   . . . . #   . . . . #
                # # # # #   # # # # #   # # # # #   # # # # #   # # # # #   # # # # #   # # # # #   # # # # #   # # # # #
                # . . . #   # . . . #   # . . . #   # . . . #   # . . . #   # . . . #   # . . . #   # . . . #   # . . . #
                `, 400)
            return score
        }
    }
}

/**
 * if any button pressed, terminate the animation immediately
 */
function splashScreen() {
    let img = images.createImage(`
        . # . # .   . # . # .   . # . # .   . # . # .   . . # . .   . # # . .
        # # # # #   # . # . #   # # # # #   # . # . #   . # . # .   . # . # .
        # # # # #   # . . . #   # # # # #   # . . . #   . # . # .   . # # . .
        . # # # .   . # . # .   . # # # .   . # . # .   . # . # .   . # . . .
        . . # . .   . . # . .   . . # . .   . . # . .   . . # . .   . # . . .
        `)
    let x = 0
    while (!aWasPressed && !bWasPressed) {
        img.showImage(x)
        basic.pause(500)
        x = x + 5
        if (x >= img.width()) {
            x = 0
        }
    }
}

/**
 * Test sensing and buzzing
 * I/O at moment is (assuming self toning beeper)
 * P0 is beeper and nose LED
 * P1 is the tweezer sense wire
 * P2 is possibly the nose button
 * If we want amplification, might have to use piezo library
 * which means using extra pins
 */
function testMode() {
    while (!(buttonA())) {
        if (pins.digitalReadPin(DigitalPin.P1) == 1) {
            pins.digitalWritePin(DigitalPin.P0, 1)
            basic.showLeds(`
                . . . . .
                . . . . #
                . . . # .
                # . # . .
                . # . . .
                `)
        } else {
            pins.digitalWritePin(DigitalPin.P0, 0)
            basic.showLeds(`
                # # # # #
                . . # . .
                . . # . .
                . . # . .
                . . # . .
                `)
        }
        basic.pause(100)
    }
}

/**
 * SENSE TWINGE/CUT FROM TWEEZERS (10ms per sample)
 * @param state TODO
 */
function getEvent(state: string): string {
    if (wasTweezers) {
        if (tweezerCount_ > 20) {
            wasTweezers = false
            tweezerCount_ = 0
            return "CUT"
        } else if (!isTweezers) {
            wasTweezers = false
            tweezerCount_ = 0
            return "TWINGE"
        }
    }
    // SENSE A OR B BUTTON PRESSES
    if (state == "CHOOSE PROC") {
        if (buttonA()) {
            return "SCROLL"
        } else if (buttonB()) {
            return "SELECT"
        }
    } else if (state == "FLATLINE") {
        if (buttonB()) {
            return "SHOCK"
        } else if (nose()) {
            return "SHOCK"
        }
    } else if (state == "HEALTHY") {
        if (buttonB()) {
            return "DONE"
        }
    }
    // Clear any flags for unnecessary events in a given state to prevent latching
    aWasPressed = false
    bWasPressed = false
    return "NONE"
}

function flashDigit(digit: number) {
    for (let i = 0; i < 4; i++) {
        showDigit(digit)
        basic.pause(200)
        basic.clearScreen()
        basic.pause(200)
    }
    showDigit(digit)
    basic.pause(1000)
}

/**
 * Make a short beep sound
 */
function beep() {
    beepOn()
    basic.pause(200)
    beepOff()
}

/**
 * work out score for a procedure
 * @param procNumber TODO
 */
function pointsForProc(procNumber: number): number {
    if (procNumber < 4) {
        return 1
    } else if (procNumber < 7) {
        return 2
    } else {
        return 3
    }
}

/**
 * beep n times, for a total duration of m
 * @param times TODO
 * @param duration TODO
 */
function beepNTimesFor(times: number, duration: number) {
    let halfCycle = duration / (times * 2)
    for (let i = 0; i < times; i++) {
        beepOn()
        basic.pause(halfCycle)
        beepOff()
        basic.pause(halfCycle)
    }
}

function startIOMonitor() {
    aWasPressed = false
    input.onButtonPressed(Button.A, () => {
        aWasPressed = true
    })
    bWasPressed = false
    input.onButtonPressed(Button.B, () => {
        bWasPressed = true
    })
    wasTweezers = false
    isTweezers = false
    tweezerCount_ = 0
    control.inBackground(() => {
        let buzzCount = 0
        while (true) {
            if (pins.digitalReadPin(DigitalPin.P0) == 1) {
                wasTweezers = true
                isTweezers = true
                tweezerCount_ = tweezerCount_ + 1
                if (buzzCount == 0) {
                    pins.analogWritePin(AnalogPin.P2, 512)
                    pins.analogSetPeriod(AnalogPin.P2, 5000)
                }
                buzzCount = 10
            } else {
                isTweezers = false
            }
            if (buzzCount > 0) {
                buzzCount = buzzCount - 1
                if (buzzCount == 0) {
                    pins.analogWritePin(AnalogPin.P2, 0)
                }
            }
            basic.pause(10)
        }
    })
}

/**
 * Shows a single digit, with a nicer font than the standard micro:bit font
 * @param digit TODO
 */
function showDigit(digit: number) {
    digit = Math.clamp(0, 9, digit)
    digitsImg.showImage(digit * 5)
}

/**
 * check to see if button A was pressed recently
 */
function buttonA(): boolean {
    if (aWasPressed) {
        aWasPressed = false
        return true
    }
    return false
}

function waitButtonA() {
    while (!(buttonA())) {
        basic.pause(100)
    }
}

function buttonB(): boolean {
    if (bWasPressed) {
        bWasPressed = false
        return true
    }
    return false
}

function waitButtonB() {
    while (!(buttonB())) {
        basic.pause(100)
    }
}

function beepOn() {
    pins.analogWritePin(AnalogPin.P2, 512)
    pins.analogSetPeriod(AnalogPin.P2, 2272)
}

function beepOff() {
    pins.analogWritePin(AnalogPin.P2, 0)
}

function resetButtons() {
    aWasPressed = false
    bWasPressed = false
}

function ecg(speed: number) {
    beepOn()
    pins.digitalWritePin(DigitalPin.P1, 1)
    basic.pause(50)
    beepOff()
    basic.showAnimation(`
        . . . . #   . . . # .   . . # . .   . # . . .   # . . . .   . . . . .   . . . . .
        . . . . #   . . . # .   . . # . .   . # . . .   # . . . .   . . . . .   . . . . .
        . . . . #   . . . # .   . . # . .   . # . . .   # . . . .   . . . . .   . . . . .
        . . . . #   . . . # #   . . # # .   . # # . .   # # . . .   # . . . .   . . . . .
        # # # # #   # # # # #   # # # # #   # # # # #   # # # # #   # # # # #   # # # # #
        `, speed)
    pins.digitalWritePin(DigitalPin.P1, 0)
    basic.pause(speed * 10)
}

function nose(): boolean {
    if (wasNose) {
        wasNose = false
        return true
    }
    return false
}

/**
 * start period in microseconds
 */
function charging() {
    let period = 2000
    let dec = 500
    pins.analogWritePin(AnalogPin.P2, 512)
    pins.analogSetPeriod(AnalogPin.P2, period)
    basic.showLeds(`
        . # # . .
        . . . # .
        . . # . .
        . . . # .
        . # # . .
        `)
    basic.pause(500)
    pins.analogSetPeriod(AnalogPin.P2, period - dec)
    basic.showLeds(`
        . # # . .
        . . . # .
        . . # . .
        . # . . .
        . # # # .
        `)
    basic.pause(500)
    pins.analogSetPeriod(AnalogPin.P2, period - dec * 2)
    basic.showLeds(`
        . . # . .
        . # # . .
        . . # . .
        . . # . .
        . # # # .
        `)
    basic.pause(500)
    beepOff()
}