//-------------------------------------------------
//          TestSuite: Memtest
//-------------------------------------------------

#include "lcd.h"
#include "util.h"
#include "os_core.h"
#include "os_scheduler.h"
#include "os_memory.h"
#include "os_memheap_drivers.h"

#include <avr/interrupt.h>
#include <string.h>

#define DELAY 100

#define BITS (13)

#define SIZE (1 << BITS)

typedef uint8_t Factor;

#define MEMTEST(NAME) void NAME(Factor f, uint8_t round, uint16_t i, uint32_t* errors)

char performTest(uint8_t testId, uint8_t iterations, uint16_t max, bool errorsAsHex, prog_char const* testName, MEMTEST((*test))) {
  void renderError(uint32_t errors, bool errorsAsHex, bool show0) {
    if (!show0 && errors)
      lcd_writeChar('(');
    if (show0 || errors) {
      if (errorsAsHex)
        lcd_writeHexWord(errors);
      else {
        if (errors >= (1ul << 16)) {
          lcd_writeChar('>');
          lcd_writeDec(-1);
        } else {
          lcd_writeDec(errors);
        }
      }
    }
    if (!show0 && errors)
      lcd_writeChar(')');
  }
  lcd_clear();
  lcd_writeProgString(PSTR("Testing "));
  lcd_writeProgString(testName);
  // note: only use primes (including 1)
  uint8_t const factors[] = {1, 7, 13, 29, 101, 127};
  Factor f;
  uint32_t errors = 0;

  delayMs(10*DELAY);
  for (f = 0; f < sizeof(factors); f++) {
    lcd_clear();
    lcd_writeDec(f+1);
    lcd_writeChar('/');
    lcd_writeDec(sizeof(factors));
    lcd_writeChar(' ');
    renderError(errors, errorsAsHex, false);
    uint8_t round;
    for (round = 0; round < iterations; round++) {
      MemAddr i;
      for (i = 0; i < max; i++) {
        test(factors[f], round, i, &errors);
      }
    }
    delayMs(DELAY);
  }

  lcd_clear();
  lcd_writeProgString(PSTR("Test #"));
  lcd_writeDec(testId);
  lcd_writeChar(':');
  lcd_line2();
  if (errors) {
    lcd_writeProgString(PSTR("FEHLER: "));
    renderError(errors, errorsAsHex, true);
    delayMs(40*DELAY);
  } else {
    lcd_writeProgString(PSTR("Test successful!"));
  }
  delayMs(10*DELAY);
  lcd_clear();

  MemAddr i;
  for (i = 0; i < SIZE; i++)
    extHeap->driver->write(i, 0);

  return !!errors;
}

PROGRAM(1, AUTOSTART) {
  uint8_t pass = 0;
  uint8_t testId = 0;

  MEMTEST(doubleAccess) {
    extHeap->driver->write(i, i*f);
    *errors += extHeap->driver->read(i) != (0xFF & (i*f));
  }
  pass += !performTest(++testId, 1, SIZE, false, PSTR("double access"), doubleAccess);

  MEMTEST(addressBits) {
    uint8_t const j = (round & 1)?(BITS - i - 1):i;
    uint8_t const pat = j*f?j*f:0xFF;
    extHeap->driver->write(0, 0xFF);
    extHeap->driver->write(0xFF, 0);
    *errors |= (extHeap->driver->read(0) ^ 0xFF);
    extHeap->driver->read(0xFF);
    extHeap->driver->write(0, 0);
    extHeap->driver->write(0xFF, 0xFF);
    *errors |= (extHeap->driver->read(0));
    extHeap->driver->write(1ul << j, pat);
    *errors |= ((uint16_t)(extHeap->driver->read(0) != 0)) << j;
  }
  pass += !performTest(++testId, 2, BITS, true, PSTR("address bits"), addressBits);

  MEMTEST(integrity) {
    if (round == 0)
      extHeap->driver->write(i, i*f);
    else if (round == 1)
      *errors += extHeap->driver->read(i) != (0xFF & (i*f));
  }
  pass += !performTest(++testId, 2, SIZE, false, PSTR("memory integrity"), integrity);

  MEMTEST(inversions) {
    MemValue const pat = i*f ^ (uint8_t)-(round&1);
    // note f and 2 have no factor in common, so (i->f*i) is a permutation on Z/(2**13)Z
    MemAddr const p = (i*f) % SIZE;
    if (round)
      *errors += extHeap->driver->read(p) != pat;
    extHeap->driver->write(p, pat ^ 0xFF);
  }
  pass += !performTest(++testId, 6, SIZE, false, PSTR("inversions"), inversions);

  if (pass == testId) {
    lcd_writeProgString(PSTR("All tests passed"));
  } else {
    lcd_writeProgString(PSTR("Failed tests: "));
    lcd_writeDec(testId-pass);
  }
}
