/*
 * Zeigerbeispiel
 *
 * Dieses Programm kann sowohl unter Linux als auch auf dem SPiCboard
 * ausgeführt werden. Im Normalfall wird vor der Ausgabe des Ergebnisses
 * gewartet. Wenn mit dem Makro 'NOWAIT' kompiliert wird, wird vor der
 * Ausgabe nicht gewartet. Bei Linux wird auf einen beliebigen Tasten-
 * druck gewartet, beim AVR wird auf einen Klick auf BUTTON0 gewartet.
 *
 * Unter Linux kann dieses Programm mit
 *   gcc -g -O0 -no-pie -fno-pic -std=c11 -o pointer pointer.c && ./pointer
 * übersetzt und ausgefuehrt werden. Nach der Ausgabe stoppt das Programm.
 *
 * Beim AVR muss eine serielle Verbindung aufgebaut werden. Auf dieser
 * schreibt der AVR die Ergebnisse. Siehe auch:
 * https://www4.cs.fau.de/Lehre/current/V_SPIC/SPiCboard/group__Console.shtml
 *
 * *** Anleitung für Atmel Studio ***
 * Auf dem SPiCboard muss nach dem Flashen zuerst eine serielle Verbindung
 * hergestellt werden, in Atmel Studio 7 z.B. mit verbundenem SPiCboard
 *	- im Menü "Tools" den "Data Visualizer" starten
 *	- auf der Seite "Configuration" auswählen
 *	- in der Gruppe "Modules" den Punkt "External Connection" erweitern
 *	  und "Serial Port" auswählen.
 *	- Das neu angezeigte "Serial Port Control Panel" anpassen:
 *		- Verbindung wählen, etwa "mEDBG Virtual COM Port (COM4)"
 *		- Die Übertragungsrate ("Baud rate") auf "38400" (38 400 bit/s) setzen
 *		- Parität ("Parity") mit "None" deaktiveren
 *		- Einfaches Stoppbit ("Stop bits" auf "1")
 *		- Haken bei "DTR" (und "RTS" auf langsamen Systemen)
 *		- "Connect" zum verbinden
 * Nun auf dem SPiCboard den Taster drücken, um einen Durchlauf zu starten.
 * Die Ausgabe erscheint im Fenster.
 */

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

// Prüfe ob wir für das SPiCboard übersetzen
// (das magische Makro "__AVR__" ist in diesem Fall definiert)
#ifdef __AVR__
	// Beim SPiCboard Ausgabe über die serielle Konsole
	#include <avr/interrupt.h>
	#include <button.h>
	#include <console.h>
	#include <led.h>
#else
	#include <unistd.h>
	#include <termios.h>
#endif

//#define NOWAIT

static uint8_t pointerDemo(void);
static void wait(void);
static void wait_msg(const char *);

static void addOneCalledByValue(uint16_t t);
static void addOneCalledByReference(uint16_t *t);

static void nullifyArray(uint16_t *array, uint16_t size);
static void printArray(const uint16_t *array, uint16_t size);

static uint16_t * sumVariables(uint16_t a, uint16_t b);

typedef struct {
	int16_t lat;
	int16_t lon;
	int16_t alt;
} GPS_Pos;

#ifdef __AVR__
// Beim Mikrocontroller haben wir keine Parameter und keinen Rückgabewert
void main(void) {
	// Aktiviere Interrupts
	sei();

	// Initialisiere serielle Konsole
	sb_console_connect_default();
#ifndef NOWAIT
	sb_console_putString("Press button BUTTON0 to continue\n");
#endif

	// Endlosschleife
	while (1){

		// Zeige gelbes Licht an und warte auf Tastendruck (polling)
		sb_led_off(GREEN0);
		sb_led_on(YELLOW0);
		wait();

		// Zeige grünes Licht an und starte Pointer Demo
		sb_led_on(GREEN0);
		sb_led_off(YELLOW0);

		pointerDemo();
		wait();
	}
}
#else
// Bei Linuxprogrammen haben wir Parameter (argc beinhaltet die Anzahl,
// argv die Werte der Parameter) und einen Rückgabewert
int main(int argc, const char * argv[]) {
	// Führe Demo einmalig aus und beende

#ifndef NOWAIT
	printf("Press any key to continue and CTRL+C to exit\n");
	wait();
#endif

	return pointerDemo();
}
#endif

// Hauptfunktion
static uint8_t pointerDemo(void){
	printf("[PointerDemo]\n");

	// Pointer allgemein
	printf("\npointer (in general):\n");

	uint16_t t = 1302;
	wait_msg("t      => ");
	printf("%u\n", t);
	wait_msg("&t     => ");
	printf("%p\n", &t);

	uint16_t* t_ptr = &t;
	wait_msg("t_ptr  => ");
	printf("%p\n", t_ptr);

	uint16_t t2 = *t_ptr;
	wait_msg("t2     => ");
	printf("%u\n", t2);

	t = 3;
	wait_msg("t      => ");
	printf("%u\n", t);
	wait_msg("*t_ptr => ");
	printf("%u\n", *t_ptr);
	wait_msg("t2     => ");
	printf("%u\n", t2);
	wait_msg("t_ptr  => ");
	printf("%p\n", t_ptr);


	// const Pointer vs. Pointer const
	printf("\nconst pointer:\n");

	uint16_t a = 2;
	uint16_t b = 4;

	const uint16_t * int_ptr_foo = &a;
	int_ptr_foo = &b;
	wait_msg("int_ptr_foo      => ");
	printf("%p\n", int_ptr_foo);
	wait_msg("*int_ptr_foo     => ");
	printf("%u\n", *int_ptr_foo);
	// Was ist mit
	// *int_ptr_foo = 3;

	uint16_t * const int_ptr_bar = &a;
	*int_ptr_bar = 3;
	wait_msg("int_ptr_bar      => ");
	printf("%p\n", int_ptr_bar);
	wait_msg("*int_ptr_bar     => ");
	printf("%u\n", *int_ptr_bar);
	// Was ist mit
	// int_ptr_bar = &b;

	// Pointer als Parameter
	printf("\npointer as parameter:\n");

	uint16_t f = 3;
	addOneCalledByValue(f);
	wait_msg("f      => ");
	printf("%u\n", f);
	wait_msg("&f     => ");
	printf("%p\n", &f);

	addOneCalledByReference(&f);
	wait_msg("f      => ");
	printf("%u\n", f);
	wait_msg("&f     => ");
	printf("%p\n", &f);


	// Arrays
	printf("\narrays:\n");

	uint16_t array[] = {1302, 2110, 123, 456, 789};
	wait_msg("array            => ");
	printf("%p\n", array);
	wait_msg("array[0]         => ");
	printf("%u\n", array[0]);
	wait_msg("array[3]         => ");
	printf("%u\n", array[3]);
	wait_msg("&(array[2])      => ");
	printf("%p\n", &array[2]);

	uint16_t* array_ptr = array;
	wait_msg("array_ptr        => ");
	printf("%p\n", array_ptr);
	wait_msg("(*array_ptr)     => ");
	printf("%u\n", (*array_ptr));

	wait_msg("array_ptr + 2    => ");
	printf("%p\n", array_ptr + 2);
	wait_msg("*(array_ptr + 2) => ");
	printf("%u\n", *(array_ptr + 2));

	nullifyArray(array, 5);
	wait_msg("printArray(array, 5)\n");
	printArray(array, 5);


	// NULL / ungültige Zeiger
	printf("\nNULL/invalid pointer:\n");

	uint16_t *null_ptr = (uint16_t *)NULL; //0x0000000012345678
	wait_msg("null_ptr         => ");
	printf("%p\n", null_ptr);

	addOneCalledByReference(null_ptr);


	// Strukturen
	printf("\nstructs:\n");

	GPS_Pos pos = {123,456,789};
	wait_msg("pos.{lat,...}    =>\n");
	printf("pos.lat          => %i\n", pos.lat);
	printf("pos.lon          => %i\n", pos.lon);
	printf("pos.alt          => %i\n", pos.alt);

	GPS_Pos* pos_ptr = &pos;
	wait_msg("\npos_ptr          => ");
	printf("%p\n", pos_ptr);
	wait_msg("\n(*pos_ptr).{lat,...}   =>\n");
	printf("(*pos_ptr).lat        => %i\n", (*pos_ptr).lat); // . hat höhere Priorität als * !!!
	printf("(*pos_ptr).lon        => %i\n", (*pos_ptr).lon); // . hat höhere Priorität als * !!!
	printf("(*pos_ptr).alt        => %i\n", (*pos_ptr).alt); // . hat höhere Priorität als * !!!

	wait_msg("\npos_ptr->{lat,...}    => \n");
	printf("pos_ptr->lat          => %i\n", pos_ptr->lat);
	printf("pos_ptr->lon          => %i\n", pos_ptr->lon);
	printf("pos_ptr->alt          => %i\n", pos_ptr->alt);

	wait_msg("\npos_ptr          => ");
	printf("%p\n", pos_ptr);
	wait_msg("&(pos_ptr->{lat,...})  => \n");
	printf("&(pos_ptr->lat)        => %p\n", &(pos_ptr->lat));
	printf("&(pos_ptr->lon)        => %p\n", &(pos_ptr->lon));
	printf("&(pos_ptr->alt)        => %p\n", &(pos_ptr->alt));

	int16_t *lat = &(pos_ptr->lat);
	*lat = 3;
	wait_msg("\npos.lat          => ");
	printf("%i\n", pos.lat);


	// Funktionszeiger
	printf("\nfunction pointer:\n");

	void (*addOne)(uint16_t *) = addOneCalledByReference;

	uint16_t foo = 5;
	wait_msg("foo             => ");
	printf("%u\n", foo);

	addOne(&foo);
	wait_msg("foo             => ");
	printf("%u\n", foo);


	// Rückgabe von ungültigen Zeigern
	printf("\nreturning invalid pointers:\n");

	wait_msg("uint16_t *sum1_ptr = sumVariables(1, 3)\n");
	uint16_t *sum1_ptr = sumVariables(1, 3);
	wait_msg("sum1_ptr          => ");
	printf("%p\n", sum1_ptr);
	wait_msg("*sum1_ptr         => ");
	printf("%u\n", *sum1_ptr);

	wait_msg("\nuint16_t *sum2_ptr = sumVariables(5,4)\n");
	uint16_t *sum2_ptr = sumVariables(5, 4);
	wait_msg("sum1_ptr          => ");
	printf("%p\n", sum1_ptr);
	wait_msg("*sum1_ptr         => ");
	printf("%u\n", *sum1_ptr);
	wait_msg("sum2_ptr          => ");
	printf("%p\n", sum2_ptr);
	wait_msg("*sum2_ptr         => ");
	printf("%u\n", *sum2_ptr);

	// That's all folks
	printf("\n\n[PointerDemo end]\n");
	return 0;
}


static void addOneCalledByValue(uint16_t t){
	t = t + 1;
}

static void addOneCalledByReference(uint16_t *t){
	if(t != NULL){
		*t = *t + 1;
	}
}

static void nullifyArray(uint16_t *array, uint16_t size){
	for(uint16_t i = 0; i < size; i++){
		array[i] = 0;
	}
}

static void printArray(const uint16_t *array, uint16_t size){
	for(uint16_t i = 0; i < size; i++){
		printf("array[%u]         => %u\n", i, array[i]);
	}
}

static uint16_t * sumVariables(uint16_t a, uint16_t b) {
	uint16_t sum = a + b;
	printf("sum               => %u\n", sum);

	uint16_t *sum_ptr = &sum;
	printf("sum_ptr           => %p\n", sum_ptr);

	return sum_ptr; // &sum;
}

static void wait(void) {
	wait_msg(NULL);
}

static void wait_msg(const char *msg) {
	if(msg != NULL) {
		printf("%s", msg);
	}

#ifndef NOWAIT
#ifdef __AVR__
	while (sb_button_getState(BUTTON0) != PRESSED);
	while (sb_button_getState(BUTTON0) != RELEASED);
#else
	// Enable canonical mode, disable local echo -> React to any keystroke, instead of waiting for a newline
	struct termios old_tio;
	tcgetattr(STDIN_FILENO, &old_tio);

	struct termios new_tio;
	tcgetattr(STDIN_FILENO, &new_tio);

	new_tio.c_lflag &= ~(ICANON | ECHO);

	tcsetattr(STDIN_FILENO, TCSANOW, &new_tio);

	// Read a character
	FILE *stream = stdin;
	int c = fgetc(stream);

	// Restore old tio -> Revert to line buffering
	tcsetattr(STDIN_FILENO, TCSANOW, &old_tio);

	// Evaluate character
	if(c == EOF && ferror(stream)) {
		fprintf(stderr, "Error while waiting on any key\n");
		exit(EXIT_FAILURE);
	}
#endif
#endif
}
