/**
 * Tests for the radio. Press A on mbit 1 and B on mbit 2 to run the tests.
 * Sends random ints, doubles, strings, and buffers and checks them on
 * the other side
 */

class FastRandom {
    private lfsr: number;
    public seed: number;

    constructor(seed?: number) {
        if (seed === undefined) seed = Math.randomRange(0x0001, 0xFFFF);
        this.seed = seed;
        this.lfsr = seed;
    }

    next(): number {
        return this.lfsr = (this.lfsr >> 1) ^ ((-(this.lfsr & 1)) & 0xb400);
    }

    randomRange(min: number, max: number): number {
        return min + (max > min ? this.next() % (max - min + 1) : 0);
    }

    reset() {
        this.lfsr = this.seed;
    }
}

enum TestStage {
    Integer,
    String,
    Double,
    IntValue,
    DblValue,
    Buffer
}

const TEST_COUNT = 30;

radio.setGroup(78)
const rand = new FastRandom(1234);

let stage = TestStage.Integer;

function initSender() {
    let lastReceived: number;
    let lastString: string;
    let testIndex = 0;
    let lastBuf: Buffer;

    let lastAck = -1;

    rand.reset();
    basic.clearScreen();

    // Send loop
    control.inBackground(function () {
        while (true) {
            for (let i = 0; i < TEST_COUNT; i++) {
                toggle(testIndex);

                if (stage === TestStage.Integer) {
                    lastReceived = getNextInt();
                }
                else if (stage === TestStage.Double) {
                    lastReceived = getNextDouble();
                }
                else if (stage === TestStage.IntValue) {
                    lastString = getNextName();
                    console.log(truncateString(lastString, 8))
                    lastReceived = getNextInt();
                }
                else if (stage === TestStage.DblValue) {
                    lastString = getNextName();
                    lastReceived = getNextDouble();
                }
                else if (stage === TestStage.String) {
                    lastString = getNextString();
                    console.log(truncateString(lastString, 19))
                }
                else if (stage === TestStage.Buffer) {
                    lastBuf = getNextBuffer();
                }

                while (lastAck !== testIndex) {
                    if (stage === TestStage.Integer || stage === TestStage.Double) {
                        radio.sendNumber(lastReceived)
                    }
                    else if (stage === TestStage.IntValue || stage === TestStage.DblValue) {
                        radio.sendValue(lastString, lastReceived)
                    }
                    else if (stage === TestStage.String) {
                        radio.sendString(lastString);
                    }
                    else if (stage === TestStage.Buffer) {
                        radio.sendBuffer(lastBuf);
                    }
                    basic.pause(10);
                }
                testIndex++;
            }

            stage++;
            if (stage > TestStage.Buffer) {
                basic.showIcon(IconNames.Yes);
                return;
            }
        }
    })

    radio.onReceivedNumber(function (receivedNumber: number) {
        if (receivedNumber > lastAck) {
            lastAck = receivedNumber;
        }
    });
}

let lastReceived: number;
let lastString: string;
let testIndex = -1;
let running = true;
let lastBuf: Buffer;

let lastPacket = new radio.Packet();
let currentPacket = new radio.Packet();

function truncateString(str: string, bytes: number) {
    str = str.substr(0, bytes);
    let buff = control.createBufferFromUTF8(str);

    while (buff.length > bytes) {
        str = str.substr(0, str.length - 1);
        buff = control.createBufferFromUTF8(str);
    }

    return str;
}

function initReceiver() {

    rand.reset();

    basic.clearScreen();

    radio.onDataReceived(function () {
        radio.receiveNumber();

        currentPacket.receivedNumber = radio.receivedNumber();
        currentPacket.receivedString = radio.receivedString();
        currentPacket.receivedBuffer = radio.receivedBuffer();

        if (currentPacket.receivedNumber === lastPacket.receivedNumber &&
            currentPacket.receivedString === lastPacket.receivedString &&
            checkBufferEqual(currentPacket.receivedBuffer, lastPacket.receivedBuffer)) {
            return;
        }

        lastPacket.receivedNumber = currentPacket.receivedNumber
        lastPacket.receivedString = currentPacket.receivedString
        lastPacket.receivedBuffer = currentPacket.receivedBuffer

        switch (stage) {
            case TestStage.Integer:
                verifyInt(radio.receivedNumber());
                break;
            case TestStage.Double:
                verifyDouble(radio.receivedNumber());
                break;
            case TestStage.IntValue:
                verifyIntValue(radio.receivedString(), radio.receivedNumber());
                break;
            case TestStage.DblValue:
                verifyDblValue(radio.receivedString(), radio.receivedNumber());
                break;
            case TestStage.String:
                verifyString(radio.receivedString());
                break;
            case TestStage.Buffer:
                verifyBuffer(radio.receivedBuffer());
                break;
        }
    })

    control.inBackground(function () {
        while (running) {
            radio.sendNumber(testIndex);
            basic.pause(10)
        }
    })
}

function nextTest() {
    testIndex++;
    toggle(testIndex);
    console.log(`test ${testIndex}`)
    if (((testIndex + 1) % TEST_COUNT) === 0) {
        stage++;

        if (stage > TestStage.Buffer) {
            basic.showIcon(IconNames.Yes)
            running = false;
        }
    }
}

function verifyInt(int: number) {
    if (int === lastReceived) return;
    lastReceived = int;
    if (lastReceived != getNextInt()) fail();
    nextTest()
}

function verifyDouble(dbl: number) {
    if (dbl === lastReceived) return;
    lastReceived = dbl;
    if (lastReceived != getNextDouble()) fail();
    nextTest()
}

function verifyIntValue(name: string, val: number) {
    if (val === lastReceived) return;
    lastReceived = val;

    if (name != truncateString(getNextName(), 8) || lastReceived != getNextInt()) fail();
    nextTest()
}

function verifyDblValue(name: string, val: number) {
    if (val === lastReceived) return;
    lastReceived = val;

    if (name != truncateString(getNextName(), 8) || lastReceived != getNextDouble()) fail();
    nextTest()
}

function verifyString(str: string) {
    if (!str || str === lastString) return;

    lastString = str;
    let next = truncateString(getNextString(), 19);

    if (lastString !== next) {
        console.log(`got ${control.createBufferFromUTF8(lastString).toHex()} expected ${control.createBufferFromUTF8(next).toHex()}`)
    }
    nextTest()
}

function verifyBuffer(buf: Buffer) {
    if (checkBufferEqual(lastBuf, buf)) return;

    lastBuf = buf;

    if (!checkBufferEqual(lastBuf, getNextBuffer())) {
        fail();
    }
    nextTest()
}

function fail() {
    control.panic(testIndex);
}


let _lastInt: number;
let _lastDbl: number;
let _lastStr: string;
let _lastBuf: Buffer;
let _lastNam: string;

function getNextInt(): number {
    let res = rand.next();
    if (!res || res === _lastInt) return getNextInt();
    _lastInt = res;
    return res;
}

function getNextDouble(): number {
    let res = rand.next() / rand.next();
    if (res === _lastDbl) return getNextDouble();
    _lastDbl = res;
    return res;
}

function getNextString(): string {
    let len = rand.randomRange(1, 19);
    let res = "";
    for (let i = 0; i < len; i++) {
        res += String.fromCharCode(rand.next() & 0xbfff);
    }

    if (res === _lastStr) return getNextString();

    _lastStr = res;
    return res;
}

function getNextName(): string {
    let len = rand.randomRange(1, 8);
    let res = "";
    for (let i = 0; i < len; i++) {
        res += String.fromCharCode(rand.next() & 0xbfff);
    }

    if (res === _lastNam) return getNextName();

    _lastNam = res;
    return res;
}

function getNextBuffer(): Buffer {
    let len = rand.randomRange(0, 8);
    let res = control.createBuffer(len);

    for (let i = 0; i < len; i++) {
        res[i] = rand.next() & 0xff;
    }

    if (checkBufferEqual(_lastBuf, res)) return getNextBuffer();

    _lastBuf = res;
    return res;
}

function checkBufferEqual(a: Buffer, b: Buffer) {
    if (a === b) return true;
    if ((!a && b) || (a && !b)) return false;
    if (a.length != b.length) return false;
    for (let i = 0; i < a.length; i++) {
        if (a[i] !== b[i]) return false;
    }
    return true;
}

input.onButtonPressed(Button.A, function () {
    basic.showString("S");
    initSender();
})

input.onButtonPressed(Button.B, function () {
    basic.showString("R");
    initReceiver();
})

function toggle(index: number) {
    const x = index % 5;
    const y = Math.idiv(index, 5) % 5;
    led.toggle(x, y);
}