/* -------------- System Include Files ------------------- */
#include "stm32f10x_conf.h"
#include "system_stm32f10x.h"

/* -------------- Standard Include Files ---------------- */
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
/* -------------- Special Include Files / Librarys ------ */
/* -------------- Projekt nclude Files ------------------ */
#include "sysinit.h"
#include "tools.h"
#include "main.h"

/* -------------- Lokale Defintionen  ------------------- */

/* Hardware MAX7219 */

#define LED_MODULE_MAX          12          // Anzahl Module

#define MAX7219_TEST            0x0f
#define MAX7219_BRIGHTNESS      0x0a
#define MAX7219_SCAN_LIMIT      0x0b
#define MAX7219_DECODE_MODE     0x09
#define MAX7219_SHUTDOWN        0x0C
#define MAX7219_NOP             0x00

#define MAX7219_DIGIT_0         0x01
#define MAX7219_DIGIT_1         0x02
#define MAX7219_DIGIT_2         0x03
#define MAX7219_DIGIT_3         0x04
#define MAX7219_DIGIT_4         0x05
#define MAX7219_DIGIT_5         0x06
#define MAX7219_DIGIT_6         0x07
#define MAX7219_DIGIT_7         0x08

/* Spielfeld 768 LEDs, pro LED ein Byte */

#define MIN_X       0
#define MAX_X       32
#define MIN_Y       0
#define MAX_Y       24

#define DEAD        0
#define ALIVE       1

/* Geschwindigkeit der Simulation */
#define DISPLAY_SPEED  120

/* -------------- Interface Variablen ----------------------- */

/* ------------------- Prototypen  -------------------------- */
static int   CheckForPeriod(uint8_t*, uint8_t*, size_t);
static void  MatrixFillRandom();
static void  CopyNextGenToMatrix();
static void  MatrixToBitstream();
static void  handofgod(uint16_t newcells);
static void  CreateGliders(uint8_t);

/* -------------- Privat Variablen ----------------------- */
uint16_t changes = 0;
uint32_t cells = 0;

// 2 Spielfelder alt+neu
struct feld_t {
      uint8_t last[MAX_X+1][MAX_Y+1];
      uint8_t next[MAX_X+1][MAX_Y+1];
} feld;

/* Die Abbildung der Matrix auf das Display */
uint8_t matrix[MAX_X+1][MAX_Y+1];

/* Datenmatrix für alle 12 Module
   Es werden nacheinander die Zeilen 0 bis 7 aller Module gefüllt */
#define DATA_SETS   (12*8)
static uint8_t datastream[DATA_SETS+1];

/* -------------- Routinen ----------------------- */

/* Startet die Simulation */
void life()
{
    int stillcnt = 0;

    srand(RTC_GetCounter());
    /* Matrix löschen */
    ClearMatrix(0);

    MatrixFillRandom();
    for (int l = 0; l < 5; l++)
        CopyNextGenToMatrix();

    while (1) {
        CopyNextGenToMatrix();
        MatrixToBitstream();

        if (cells == 0)
            NVIC_SystemReset();

        /* Feld leer, dann Gleiter erzeugen */
        if (cells <= 15) {
            LED_ON();
            handofgod(10);
            CreateGliders(1);
        }

        if (CheckForPeriod((uint8_t*)matrix,(uint8_t*)datastream,DATA_SETS)) {
            if (stillcnt++ > 5) {
                LED_ON();
                CreateGliders(2);
            }
        }
        else {
            stillcnt  = 0;
        }

        /* Timer 2 INT holt CPU wieder aus Sleep raus */
        //MakeCPUSleep();
        DelayMs(150);
        IWDG_ReloadCounter(); // Reset WDT

        LED_OFF();
    }
}

/*  -----------------------------------------------------------------------------
    Prüft ob, der aktuelle Datenstream schonmal eingespielt wurde in den letzten
    5 Perioden.
-------------------------------------------------------------------------------- */

static
int CheckForPeriod(uint8_t* matrix, uint8_t* source, size_t len)
{
    #define NOPERIODS   5

    /* Puffer für 5 Perioden */
    static uint8_t hPtr = 0;
    static uint32_t HistoryCRC[NOPERIODS];      // CRC Prüfsummen
    static uint16_t NoOfCells[NOPERIODS];       // Anzahl Zellen

    /* Zellen des aktuellen Feldes zählen */
    for (int x = 0; x < MAX_X; x++) {
        for (int y = 0; y < MAX_Y; y++) {
            /* 2D Array Adressierung manuell berechnen */
            if (*(matrix + (x * MAX_X + y))  == ALIVE)
                NoOfCells[hPtr]++;
        }
    }

    /* Bedingungen prüfen für statisches Feld, bzw. nur Gleiter */

    /* CRC32 Summe bilden für aktuellen Datenstrom */
    CRC_ResetDR();
    uint32_t crc = CRC_CalcBlockCRC((uint32_t*) source, len/4);
    HistoryCRC[hPtr] = crc;

    /* Neuen Datenstrom gegen alle anderen vergleichen */
    uint8_t cmpPtr = (hPtr + 1) % NOPERIODS;

    uint8_t cnt = 0;
    while ((HistoryCRC[cmpPtr] != crc ) && (cnt < (NOPERIODS-1))) {
        cmpPtr = (cmpPtr + 1) % NOPERIODS;
        cnt++;
    };

    /* Cnt < 4, dann war Treffer */
    int treffer = (cnt < (NOPERIODS-1)) ? true:false;

    /* Ringpuffer rotieren */
    hPtr = (hPtr + 1) % NOPERIODS;

    return (treffer);
}

// Setze ein zufälliges Startmuster
void MatrixFillRandom()
{
    #define MAX_ZELLEN  ((MAX_X * MAX_Y) / 3)

    srand(RTC_GetCounter());
    uint16_t k = MAX_ZELLEN;
    do {
         uint16_t x = rand() % (MAX_X);
         uint16_t y = rand() % (MAX_Y);
         if (feld.last[x][y] != ALIVE) {
             k--;
            // Markiere Spielfeld
            feld.last[x][y] = ALIVE;
            // Zeichne Pixel auf LCD
            matrix[x][y] = ALIVE;
         }
    } while (k > 0);
}


/* Erzeugt eine definierbare Anzahl von Gleitern mit
   zufälliger Flugrichtung  */

static void CreateGliders(uint8_t n)
{
    static int orientation = 0;

   	static
    const uint8_t glider[4][3][3] = { { {1,1,1}, {0,0,1}, {0,1,0}  },
                                      { {0,1,0}, {0,0,1}, {1,1,1}  },
                                      { {1,0,0}, {1,0,1}, {1,1,0}  },
                                      { {1,1,1}, {1,0,0}, {0,1,0}}
									};

	/* Erzeuge n zufällige Positionen und Ausrichtungen */
	while (n--) {

		/* Zufalls Koordinaten-Paar mit 3px Abstand */
		int x = 5 + (rand() % (MAX_X-5));
		int y = 5 + (rand() % (MAX_Y-5));

		/* Zufällige Ausrichtung, 0..3 */
		//int zufall = rand() % 4;
        for (int xp = 0; xp <= 2; xp++)
            for (int yp = 0; yp <= 2; yp++)
                feld.last[x + xp][y + yp] = glider[orientation][xp][yp];

        orientation = (orientation + 1) % 4;
	};

}

// Zufallsereignisse, neue Zellen erschaffen
static
void handofgod(uint16_t newcells)
{
    /* Hand Gottes: Neue unsichtbare Zellen wahllos entstehen
       lassen. Dadurch werden feste Strukturen verhindert und
       die Gesellschaft bleibt dauerhaft am Leben */

    srand(RTC_GetCounter());

    for (uint16_t i = 0; i<newcells;i++) {
       feld.last[rand() % MAX_X][rand() % MAX_Y] = ALIVE;
    }
}

/* Überträgt die Info aus dem Berechnungsfeld in das Darstellungsfeld */
void CopyNextGenToMatrix()
{
    changes = 0;
    cells = 0;

    /* Kalkulationsfeld löschen */
    memset(feld.next,DEAD,sizeof(feld.next));

    // Durchsuche Feld nach LIVE Zellen
    for (int xpos = 0;xpos < MAX_X; xpos++) {
     for (int ypos = 0;ypos < MAX_Y; ypos++)  {
        if (feld.last[xpos][ypos] >= ALIVE)  {

          // Ermittle den Zustand der 8 Nachbarn
          cells++;
          for (int y=(ypos-1); y<=(ypos+1); y++)  {
            for(int x=(xpos-1); x<=(xpos+1); x++)  {
               // Koord.transformation auf gegenüber liegenden Seiten
               int x1 = x;
               int y1 = y;

               if (x < 0 )
                    x1 = MAX_X-1;   // x Koordinate
               else if (x > (MAX_X-1))
                   x1 = 0;

               if (y < 0)
                   y1 = MAX_Y-1;    // y Koordinate
               else if (y > (MAX_Y-1))
                   y1 = 0;

               // Erhöhe Nachbarpunkte um 1
               feld.next[x1][y1]++;
            }
          }
          // Auf Zell Koordinate wieder 1 abziehen
          feld.next[xpos][ypos]--;
        }
      }
    }

    /* Das Zwischenergebnis auswerten und darstellen */
    for (int x = 0; x < MAX_X; x++)   {
        for (int y = 0; y < MAX_Y; y++)  {

            /* Zeiger auf aktuelle Zelle */
            uint8_t *foo = &feld.last[x][y];

            /* Bewerte Anzahl der Nachbarn pro Zelle x/y */
            switch (feld.next[x][y]) {

              case 2: // Zelle lebt und bleibt am Leben
                      if (*foo >= ALIVE)
                        matrix[x][y] = ALIVE;
                      break;
              case 3:
                      // Zelle lebt und bleibt am Leben
                      *foo = ALIVE;
                      matrix[x][y] = ALIVE;
                      break;
              default:
                      // Zelle war lebendig und stirbt
                      if (*foo >= ALIVE) {
                          *foo = DEAD;
                           matrix[x][y] = DEAD;
                           changes++;
                      }
                      break;
              }
        }
    }
}

/* Berechnet die Digit-Position im Stream für x/y Koordinate
   Ein Zeilensatz beinhaltet je 12 Bytes, für jedes Modul 1 Byte für Digit-0
   Der nächste Zeilensatz sind dann 12 Bytes für Digit-1 usw

   Digit    0          1           2            3          4           5            6           7
   Byte Nr  0..........12..........24..........36..........48..........60..........72..........84........
*/
static
void CalcPos(int* bitpos, int* bytepos, int x, int y)
{
    /* Werte begrenzen */
	x = x % MAX_X;
    y = y % MAX_Y;

	static const uint8_t lut[MAX_Y]={0,12,24,36,48,60,72,84,
                                     4,16,28,40,52,64,76,88,
                                     8,20,32,44,56,68,80,92};

	*bytepos = lut[y] + (x/8);
	*bitpos  = 7- (x % 8);

	return;
}

/* Überträgt die GOL Matrix in einen Bitstream, der ins Display geladen werden kann */
static
void MatrixToBitstream()
{
    int bitPos, BytePos;

    /* Matrix zeilenweise von links nach rechts lesen */
    for (int y = 0; y < MAX_Y; y++) {
        for (int x = 0; x < MAX_X; x++)
        {
            /* Berechne Position*/
            CalcPos(&bitPos,&BytePos,x,y);
            if (matrix[x][y])      /* Zelle lebt, also 1 */
                //datastream[BytePos] |= (1 << bitPos);
                set_bit((uint32_t*)&datastream[BytePos],bitPos,1);
            else                  /* Zelle ist tot, also 0 */
                //datastream[BytePos] &= ~(1 << bitPos);
                set_bit((uint32_t*)&datastream[BytePos],bitPos,0);
        }
    }
}

/* Löscht alle Felder */
void ClearMatrix(uint8_t val)
{
   for (int i = 0; i <= DATA_SETS; i++)
       datastream[i] = val;

   memset(&matrix,0,sizeof(matrix));
   memset(&feld.last,0,sizeof(feld.last));
   memset(&feld.next,0,sizeof(feld.next));
   memset(&datastream,0,sizeof(datastream));
}

/* Init Matrix LED */
void InitMatrix()
{
    /* Module ausschalten */
    for (int i = 0; i < LED_MODULE_MAX; i++) {
        CE_LOW();
        SPI_SendWord((uint16_t)(MAX7219_SHUTDOWN << 8) + 0);
        CE_HIGH();
    }
    DelayMs(100);

    /* Decode Mode ausschalten */
    for (int i = 0; i < LED_MODULE_MAX; i++) {
        CE_LOW();
        SPI_SendWord((uint16_t)(MAX7219_DECODE_MODE << 8) + 0);
        CE_HIGH();
    }
    /* Scan Limit auf 8 LED setzen */
    for (int i = 0; i < LED_MODULE_MAX; i++) {
        CE_LOW();
        SPI_SendWord((uint16_t)(MAX7219_SCAN_LIMIT << 8) + 7);
        CE_HIGH();
    }
    /* Helligkeit setzen */
    for (int i = 0; i < LED_MODULE_MAX; i++) {
        CE_LOW();
        SPI_SendWord((uint16_t)(MAX7219_BRIGHTNESS << 8) + 2);
        CE_HIGH();
    }
    /* Module aufwecken */
    for (int i = 0; i < LED_MODULE_MAX; i++) {
        CE_LOW();
        SPI_SendWord((uint16_t)(MAX7219_SHUTDOWN << 8) + 1);
        CE_HIGH();
    }
}

/* ---------- Event Timer für Timer 3 Reload  ---------- */
void TIM3_IRQHandler()
{
    static uint32_t CRC32Alt = 0;

    /* Fehl IRQ Anforderungen ausblenden */
    if (TIM_GetITStatus(TIM3, TIM_IT_Update) == RESET)
       return;

    /* Bilde die CRC32 Checksumme über den Bitstream */
    CRC_ResetDR();
    uint32_t CRC32Neu = CRC_CalcBlockCRC((uint32_t*)datastream,sizeof(datastream)/4);
    if (CRC32Neu != CRC32Alt) {
        /* Füllt alle Datenbereiche 0..7 aller 12 Module auf */
        int datacnt = DATA_SETS-1;
        for (int digit = 0; digit < 8; digit++) {
            CE_LOW();
            for (int i = 0; i < LED_MODULE_MAX; i++)
               SPI_SendWord(((uint16_t)(MAX7219_DIGIT_0 + digit) << 8) | datastream[datacnt--]);
            CE_HIGH();

        }
        CRC32Alt = CRC32Neu;
    }

    /* Clear IRQ Flag */
    TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
}

/* Bitbanding: Setzt ein Bit einer Variablen */
void set_bit(uint32_t* addr, const int bit, _Bool val)
{
     *((volatile uint32_t*)(32 * (((uint32_t)addr) - 0x20000000) + 0x22000000 + 4 * bit)) = val;
}

/* Bitbanding Teste ein Bit. Rückgabe: Bit */
uint8_t test_bit(uint32_t* addr, const int bit)
{
     return *((volatile uint32_t*)(32 * (((uint32_t)addr) - 0x20000000) + 0x22000000 + 4 * bit));
}
