#include <stdio.h>
#include <stdint.h>
#include <esp32-c3_regs.h>  // [ANGEPASSTE HEADER-DATEI mit GPIO_ENABLE_W1TS_REG VERWENDEN!!!!!!!!]
#include <calibrate_adc.c>

//----------GLOBALE KONSTANTEN (Bei Bedarf beliebig anpassen!)----------//
#define LEDS 16                               // 16 physische LEDS
#define PIXEL_PER_LED 3                       // 3 Pixel pro LED
#define PIXEL_ARRAY_SIZE LEDS * PIXEL_PER_LED // 16 * 3 = 48 Pixel
uint8_t pixels[PIXEL_ARRAY_SIZE];             // Globales Array fuer 48 Pixel mit je 8 Bit = 384 Bit
#define MIN_DISTANCE 5                        // Minimale Sensordistanz
#define MAX_DISTANCE 50                       // Maximale Sensordistanz   [SENSORDISTANZ IST HIER EINSTELLBAR!!!!!!!!!!!!!!!!!! ZUSATZAUFGABE???]
#define TOTAL_MEASUREMENTS_FOR_AVERAGE 10      // Anzahl Messungen für einen Mittelwert
#define MAX_DISTANCE_DEVIATION_PERCENT 20      // +/- Maximale Abweichung in Prozent

//----------INITIALISIERUNG UND GLOBALE FUNKTIONEN----------//
void gpio_init()
{
    *GPIO_ENABLE_W1TS_REG |= (0b1 << 0);  // Enable GPIO0 als Output (Trigger)
    *GPIO_ENABLE_W1TS_REG |= (0b1 << 2);  // Enable GPIO2 als Output (LED)
    *GPIO_ENABLE_W1TS_REG |= (0b1 << 8);  // Enable GPIO8 als Output (LED)
    *IO_MUX_GPIO1_REG |= (0b1 << 9);  	  // Enable GPIO1 als Input (Echo)
    *IO_MUX_GPIO3_REG |= (0b1 << 9);      // Enable GPIO3 als Input (Poti)
}

void adc_init()
{
    *APB_SARADC_CTRL_REG |= (0b1 << 0);               // ADC per Software Variante auswählen
    *APB_SARADC_CTRL_REG |= (0b1 << 1);               // ADC per Software starten
    *APB_SARADC_ONETIME_SAMPLE_REG |= (0b11 << 23);   // Dämpfung auf 12dB
    *APB_SARADC_ONETIME_SAMPLE_REG &= ~(0b1111 << 25);// Vorige Kanalauswahl löschen
    *APB_SARADC_ONETIME_SAMPLE_REG |= (3 << 25);      // Kanal 3 auswählen
    *APB_SARADC_ONETIME_SAMPLE_REG |= (0b1 << 31);    // Enable OneTimeSampling ADC1
    *APB_SARADC_INT_ENA_REG |= (0b1 << 31);           // Enable Done-Interrupt ADC1
    adc_calibrate(1);                                 // Kalibriere ADC1
}

void timer_init()
{
    *TIMG_T0CONFIG_REG &= ~(0b1 << 9);    // APB_CLK nutzen
    *TIMG_T0CONFIG_REG |= (80 << 13);	  // Divider setzen (f = 80MHz / 80 = 1MHz ==> T = 1us)
	*TIMG_T0CONFIG_REG |= (0b1 << 30);    // Increase Counter
}

void reset_timer()
{
    *TIMG_T0LOADLO_REG |= (0b0 <<0);      // Startzeit = 0 (untere 32 Bit)
    *TIMG_T0LOADHI_REG |= (0b0 <<0);      // Startzeit = 0 (obere 22 Bit)
    *TIMG_T0LOAD_REG |= (0b0 <<0);        // Startzeit laden
}

void delay_ns(unsigned int ns)          // Delay in Nanosekunden (1 Takt = 43ns)
{
    asm volatile(
        //Berechnung der Taktanzahl
        "li t1, 43            \n\t"         // t1 = 43 (1 Takt = 43ns)
        "li t0, 21            \n\t"         // t0 = 43/2 = 21 (Rundung)
        "add t0, t0, %0       \n\t"         // t0 = 21 + xns (Rundung)
        "div t0, t0, t1       \n\t"         // t0 = (21 + xns) / 43 (Takte gerundet)
        "addi t0, t0, -5      \n\t"         // t0 = Takte - 5 (nop-Takte ohne Initialisierung)
        //NOP-Schleife
        "1:                   \n\t"
        "nop                  \n\t"
        "addi t0, t0, -1      \n\t"         // t0--
        "bnez t0, 1b          \n\t"         // while(t0 != 0) -> 1:
        //Parameter
        :                                   // Ausgabe: -
        : "r" (ns)                          // Eingabe: Nanosekunden in a0
        : "t0", "t1"                        // Clobber-Register
    );
}

//----------LEDS----------//
void write_one(int gpio) 
{
    *GPIO_OUT_W1TS_REG |= (0b1 << gpio);     // GPIO2 setzen
    delay_ns(600);                           // 600ns warten
    *GPIO_OUT_W1TC_REG |= (0b1 << gpio);     // GPIO2 ruecksetzen
    delay_ns(600);                        // 600ns warten
}

void write_zero(int gpio)
{
    *GPIO_OUT_W1TS_REG |= (0b1 << gpio);     // GPIO2 setzen
    delay_ns(300);                        // 300ns warten
    *GPIO_OUT_W1TC_REG |= (0b1 << gpio);     // GPIO2 ruecksetzen
    delay_ns(900);                        // 900ns warten
}

void display_pixelsArray_LEDstripe()
{
    for (int i = 0; i < PIXEL_ARRAY_SIZE; i++) // Fuer alle 48 Pixel
    {
        for (int k = 7; k >=0 ; k--)         // Fuer alle 8 Bit jedes Pixels
        {
        if (pixels[i] & (0b1 << k))       // Jedes Bit pruefen
        {
            write_one(2);                    // Bei 1: Schreib 1
        }
        else 
        {
            write_zero(2);                   // Bei 0: Schreib 0
        }
        }
    }
  delay_ns(50000);                      // 50us warten damit die LEDs die Daten uebernehmen (siehe Datenblatt)
}



void display_pixelsArray_onboardLED()
{
    for (int i = 0; i < 3; i++) // Fuer alle 48 Pixel
    {
        for (int k = 7; k >=0 ; k--)         // Fuer alle 8 Bit jedes Pixels
        {
        if (pixels[i] & (0b1 << k))       // Jedes Bit pruefen
        {
            write_one(8);                    // Bei 1: Schreib 1
        }
        else 
        {
            write_zero(8);                   // Bei 0: Schreib 0
        }
        }
    }
  delay_ns(50000);                      // 50us warten damit die LEDs die Daten uebernehmen (siehe Datenblatt)
}





//----------ULTRASCHALLSENSOR----------//
int read_distance_cm()
{
    int distanceLow32 = 0;
    reset_timer();                              // Timer auf 0s

    // Trigger
    *GPIO_OUT_W1TS_REG |= (0b1 << 0);           // GPIO0 (Trigger) setzen
    delay_ns(11000);                            // 11us warten
    *GPIO_OUT_W1TC_REG |= (0b1 << 0);           // GPIO0 (Trigger) ruecksetzen

    // Echo + Timer
    while (!(*GPIO_IN_REG &= (0b1 << 1))){}     // Warten bis Echo Signal kommt
    *TIMG_T0CONFIG_REG |= (0b1 << 31);          // TIMER STARTEN
    while (*GPIO_IN_REG &= (0b1 << 1)) {}       // Warten solange Echo-Signal da ist
    *TIMG_T0CONFIG_REG &= ~(0b1 << 31);         // TIMER STOPPEN

    // Timer auslesen
    *TIMG_T0UPDATE_REG |= (0b1 << 0);           // Timerwertausgabe beauftragen
    while (*TIMG_T0UPDATE_REG &= (0b1 << 1)) {} // Warten bis Timerwert da ist
    distanceLow32 = *TIMG_T0LO_REG;             // untere 32 Bit des Timers auslesen
    //(obere 22 Bit werden nicht benötigt, da (2^32)/16 = 268s, Echo ist jedoch maximal 38ms lang)

    // Timerwert verarbeiten
    int distanceCm = distanceLow32 / 58;                  // Umrechnung in cm (siehe Datenblatt des HC-SR04)

    if(distanceCm < 2 || distanceCm > 400)
    {
        return -1;
    }
    else
    {
        return distanceCm;
    }
}


int calc_averageDistanceCorrected()
{
    int distancesArray[TOTAL_MEASUREMENTS_FOR_AVERAGE];                // Array für Mittelwert
    int averageDistance = 0;                              // Mittelwert
    int averageDistanceCorrected = 0;                     // Mittelwert ohne Ausreisser

    // Berechnung des Mittelwerts
    for(int i = 0; i< TOTAL_MEASUREMENTS_FOR_AVERAGE; i++)
    {
        distancesArray[i] = read_distance_cm();                 // Entfernungsmessung starten und Abstand in cm in Array auslesen
        if(distancesArray[i] == -1)
        {
            distancesArray[i] = distancesArray[i-1];
        }
        averageDistance += distancesArray[i];                   // Alle Werte aufsummieren
        //delay_ns(10000);
    }
    averageDistance = averageDistance / TOTAL_MEASUREMENTS_FOR_AVERAGE;    // Mittelwert bilden


    // Entfernung abweichender Werte und erneute Bildung des Mittelwerts
    int floorDistanceDeviation = averageDistance - (averageDistance * MAX_DISTANCE_DEVIATION_PERCENT / 100); // Untere minimale Abweichung
    int ceilDistanceDeviation = averageDistance + (averageDistance * MAX_DISTANCE_DEVIATION_PERCENT / 100); // Obere maximale Abweichung

    for(int i = 0; i< TOTAL_MEASUREMENTS_FOR_AVERAGE; i++)
    {
        if(distancesArray[i] < floorDistanceDeviation || distancesArray[i] > ceilDistanceDeviation)
        {
        distancesArray[i] = averageDistance;              // Falls Abweichung zu groß, wird der Wert auf Mittelwert gesetzt
        }
        averageDistanceCorrected += distancesArray[i];
    }
    averageDistanceCorrected = averageDistanceCorrected / TOTAL_MEASUREMENTS_FOR_AVERAGE;     // Mittelwert der korrigierten Messwerte bilden

    return averageDistanceCorrected;
}


//----------POTENTIOMETER----------//

int adc_read()
{
    *APB_SARADC_INT_CLR_REG |= (0b1 << 31);                 // Done-Bit Clearen
    *APB_SARADC_ONETIME_SAMPLE_REG |= (0b1 << 29);          // One-Time Sampling starten
    delay_ns(500000);
    *APB_SARADC_ONETIME_SAMPLE_REG &= ~(0b1 << 29);         // One-Time Sampling stoppen
    while(!(*APB_SARADC_INT_RAW_REG & (0b1 << 31) >> 31)){} // Warten bis Konvertierung abgeschlossen
  
    return (*APB_SARADC_1_DATA_STATUS_REG &= (0xFFF));      // Digitalwert auslesen
}



//----------LOOP----------//

void fill_pixelsArray(int distance, int maxBrightness)
{
    int range = MAX_DISTANCE - MIN_DISTANCE;

    int Red_Value = ((distance * 100 / 50) * maxBrightness / 100);
    int Green_Value = maxBrightness - ((distance * 100 / 50) * maxBrightness / 100);
    int Blue_Value = 0;


    for (int i = 0; i < PIXEL_ARRAY_SIZE; i++) 
    {
        pixels[i] = 0; // Initialisiere alle Pixel auf 0
    }

    if (distance > MAX_DISTANCE)     // Bei d > MAX_DISTANCE bleiben alle Pixel aus
    {
        return;
    } 
    else if (distance <= MIN_DISTANCE) // Bei d<= 5 werden alle Pixel maximal hell
    {  
        for (int i = 0; i < PIXEL_ARRAY_SIZE; i+=3) 
        {
            pixels[i] = 0;
            pixels[i + 1] = maxBrightness;
            pixels[i + 2] = 0;
        }
    }
    else // Bei MIN_DISTANCE < d <= MAX_DISTANCE
    {
        // Berechnung der Pixel der aktiven LEDs
        int scaledDistance = (distance - MIN_DISTANCE) * maxBrightness / range;                     // Skaliere d auf 0 bis maxBrightness
        int fullActivePixels = (maxBrightness - scaledDistance) * LEDS / maxBrightness;             // Anzahl vollständiger LEDs
        int partActivePixelsBrightness = (maxBrightness - scaledDistance) * LEDS % maxBrightness;   // Wert der letzten LED

        // Alle Pixel der vollständigen LEDs aktivieren
        for (int i = 0; i < fullActivePixels * PIXEL_PER_LED; i += 3) 
        {
            pixels[i] = Red_Value;
            pixels[i + 1] = Green_Value;
            pixels[i + 2] = Blue_Value;
        }

        // Die letzte LED mit dem berechneten Wert setzen
        if (fullActivePixels < LEDS)
        {
            int partActivePixelsBrightnessValue;
            if (partActivePixelsBrightness > maxBrightness) // Abfangen zu großer Werte
            {
                partActivePixelsBrightnessValue = maxBrightness;
            }
            else 
            {
                partActivePixelsBrightnessValue = partActivePixelsBrightness;
            }

            Red_Value = ((distance * 100 / 50) * partActivePixelsBrightnessValue / 100);
            Green_Value = partActivePixelsBrightnessValue - ((distance * 100 / 50) * partActivePixelsBrightnessValue / 100);
            Blue_Value = 0;
            pixels[fullActivePixels * PIXEL_PER_LED] = Red_Value;
            pixels[fullActivePixels * PIXEL_PER_LED + 1] = Green_Value;
            pixels[fullActivePixels * PIXEL_PER_LED + 2] = Blue_Value;
        }
    }
}


//----------MAIN----------//
void app_main(void)
{
    gpio_init();
    adc_init();
    timer_init();

    while (1) 
    {
        int adcValue = adc_read();                                          // ADC Wert (0...4095)
        int maxPixelBrightness = (adcValue * 255) / 4095;                   // Helligkeitswert der Pixel (0...255)
        int averageDistanceCorrected = calc_averageDistanceCorrected();     // Korigierter Mittelwert der Distanz
        fill_pixelsArray(averageDistanceCorrected, maxPixelBrightness);     // Pixels-Array mit Helligkeitswerten belegen
    
        display_pixelsArray_LEDstripe();                                    // Pixels-Array in LEDs ausgeben
        display_pixelsArray_onboardLED();
        delay_ns(100000);                                                   // 0,1s = 100ms = 100.000us = 100.000.000ns [WIE LANGE SOLLEN WIR HIER WARTEN ?????????????]
        
        /*
        // DEBUGGING: Ausgabe aller Pixelwerte nach LEDs
        for (int i = 0; i < PIXEL_ARRAY_SIZE; i++)
        {
            printf("%d ", pixels[i]);
            if ((i + 1) % PIXEL_PER_LED == 0) printf("| "); // Gruppierung in LEDs
        }
        printf("\n");
        */
    }
}




/*
//----------BACKUP----------//


LED cm  | LED1          | LED2          | LED3   ...      | LED16
0   53    0   0   0       0   0   0       0   0   0         0   0   0
1   50    255 255 255     0   0   0       0   0   0         0   0   0
2   47    255 255 255     255 255 255     0   0   0         0   0   0
3   44    255 255 255     255 255 255     255 255 255       0   0   0
4   41    .               .               .                 .
5   38    .               .               .                 .
6   35    .               .               .                 .
7   32    .               .               .                 .
8   29    .               .               .                 .
9   26    .               .               .                 .
10  23    .               .               .                 .
11  20    .               .               .                 .
12  17    .               .               .                 .
13  14    .               .               .                 .
14  11    .               .               .                 .
15  8     .               .               .                 .
16  5     255 255 255     255 255 255     255 255 255       255 255 255


int activePixels = calc_activePixels(averageDistanceCorrected); // Aktive Pixel berechnen
printf("ActivePixels: %d | ", activePixels);

int calc_activePixels(int cm)
{
  int activePixels = 0;

  if(cm > 53)                                 // Maximaler Grenzfall 
  {
    activePixels = 0;
  }
  else if(cm <= 5)                            // Minimaler Grenzfall
  {
    activePixels = 48;
  }
  else                                        // Fuer 50 >= cm > 5
  {
    activePixels = 3 * (17 - (cm / 3));
  }

  return activePixels;
}

for (int i = 0; i < PIXEL_ARRAY_SIZE; i++)                      // Alle Pixel initial auf 0
  {
    pixels[i] = 0;
  }
for (int i = 0; i < activePixels && i < PIXEL_ARRAY_SIZE; i++)  // Aktive Pixel mit Maximalwerten befuellen, Rest bleibt 0
{
  pixels[i] = pixelBrightness;
}




int sensorDistance_cm = readSensor_cm();                        // Entfernungsmessung starten und Abstand in cm auslesen
printf("Abstand: %dcm\n", sensorDistance_cm);



void write_zero()
{
  asm volatile(
    // 1 schreiben
    "li t1, 4					    \n\t" // t1 = 2
    "lw t2, %0					  \n\t"	// t2 = GPIO_OUT_REG
    "or t3, t2, t1				\n\t"	// GPIO2 setzen
    "sw t3, %0					  \n\t"	// GPIO2 = 1 rausschreiben

    // 300ns warten
    delay_ns(300);

    // 0 schreiben
    "li t1, 4					\n\t"			// t1 = 2 == 0b0000 0010
    "lw t2, %0					\n\t"			// t2 = GPIO_OUT_REG
    "not t1, t1					\n\t"			// ~0b1111 1101
    "and t3, t2, t1				\n\t"			// Verunden
    "sw t3, %0					\n\t"			// GPIO2 = 0 rausschreiben

    // 950ns warten
    delay_ns(950);

    :
    : "m" (*GPIO_OUT_REG) // ÄNDERN ZU: GPIO_OUT_W1TS_REG => Wird in der Doku empfohlen! => TESTEN!!! Dann wird die 0 mit GPIO_OUT_W1TC_REG geschrieben => ASSEMBLER ANPASSEN!!
    : "t0", "t1", "t2", "t3"
  );
}

void write_one()
{
  asm volatile(
    // 1 schreiben
    "li t1, 4					\n\t"			//2 laden
    "lw t2, %0					\n\t"			//GPIO_OUT Adresse laden
    "or t3, t2, t1				\n\t"			//Bit 2 setzen(GPIO2)
    "sw t3, %0				\n\t"			//Adresse rausschreiben

    // 96 Takte = 600ns warten
    "addi t0, zero, 14        \n\t"          	// t3 = 96 (NOPs für 600ns)
    "1:                       \n\t"          	// NOP-Schleife für 600ns
    "nop                      \n\t"          	// NOP
    "addi t0, t0, -1          \n\t"          	// Decrement t3
    "bnez t0, 1b              \n\t"          	// Wiederhole, solange t3 != 0

    // 0 schreiben
    "li t1, 4					\n\t"			//2 laden
    "lw t2, %0					\n\t"			//GPIO_OUT Adresse laden
    "not t1, t1					\n\t"			// Tilde
    "and t3, t2, t1				\n\t"			// Verunden
    "sw t3, %0				\n\t"			//Adresse rausschreiben

    // 104 Takte = 650ns warten
    "addi t0, zero, 15       \n\t"           	// t3 = 104 (NOPs für 650ns)
    "2:                       \n\t"          	// NOP-Schleife für 650ns
    "nop                      \n\t"          	// NOP
    "addi t0, t0, -1          \n\t"          	// Decrement t3
    "bnez t0, 2b"          						// Wiederhole, solange t3 != 0

    :
    : "m" (*GPIO_OUT_REG) // ÄNDERN ZU: GPIO_OUT_W1TS_REG => Wird in der Doku empfohlen! => TESTEN!!! Dann wird die 0 mit GPIO_OUT_W1TC_REG geschrieben => ASSEMBLER ANPASSEN!!
    : "t0", "t1", "t2", "t3"
  );
}



void delay_us(unsigned int xus) { // Delay in Mikrosekunden (1 Takt = 43ns) // KANN WEG!!!!!!!!!!!!!!!!!
  asm volatile(
    //----Berechnung der Takteanzahl = xus / 43ns----
    "li t1, 43            \n\t" // t1 = 43
    "li t2, 1000          \n\t" // t2 = 1000
    "mul t2, %0, t2       \n\t" // t2 = us * 1000 -> Zeit in ns
    "div t2, t2, t1       \n\t" // t2 = (us * 1000) / 43 -> Takte
    "addi t2, t2, -6      \n\t" // t2 = Takte - 6 -> nop-Takte ohne Initialisierung
    "addi t0, t2, 0       \n\t" // Lade die Anzahl der Takte in t0
    //----NOP-Schleife----
    "1:                   \n\t"
    "nop                  \n\t"
    "addi t0, t0, -1      \n\t" // t0--
    "bnez t0, 1b          \n\t" // while(t0 != 0) -> 1:
    //----Parameter----
    :                           // Ausgabe:
    : "r" (xus)                 // Eingabe: Mikrosekunden in a0
    : "t0", "t1", "t2"          // Clobber-Register
  );
}


void period_trigger()
{
	GPIO_OUT_REG |= (0b1 << 0);
	asm volatile(                       // 233 Takte = 50us warten
		"addi t0, zero, 233     	\n\t"   // t3 = 96 (NOPs für 600ns)
    "1:                       \n\t"   // NOP-Schleife für 600ns
    "nop                      \n\t"   // NOP
    "addi t0, t0, -1          \n\t"   // Decrement t3
    "bnez t0, 1b              \n\t"   // Wiederhole, solange t3 != 0
    :
    :
    : "t0"
	);

	GPIO_OUT_REG &= ~(0b1 << 0);
		asm volatile(                     // 2100 Takte = 50us warten
    "addi t0, zero, 210     		\n\t" // t3 = 96 (NOPs für 600ns)
    "1:                       \n\t"   // NOP-Schleife für 600ns
    "nop                      \n\t"   // NOP
    "addi t0, t0, -1          \n\t"   // Decrement t3
    "bnez t0, 1b              \n\t"   // Wiederhole, solange t3 != 0
    :
    :
    : "t0"
	);
}

void period_trigger()
{
	*GPIO_OUT_W1TS_REG |= (0b1 << 0);
	delay_ns(50000);

	*GPIO_OUT_W1TC_REG |= (0b1 << 0);
	delay_ns(50000);
}
*/