#include <stdlib.h> // needed for malloc/free
#include <stdio.h>
#include <inttypes.h> // needed for [u]int[8/16/32/64]_t data types and
                      // associated format characters

// A simple structure to use as an example. There are two 64-bit integers
// stored inside, and we will use fgets and sscanf to populate those fields
struct example {
	uint64_t decimal;
	uint64_t hex;
};


// Helper method to print the values stored in a struct example.
void print_example(struct example *ex) {
	// The fixed-width integer types have special printing macros.
	// You need to provide the "%" sign, but the macros expand to the
	// appropriate printf formatting specifier (as defined in inttypes.h)
	printf("ex.decimal: %"PRIu64"\n", ex->decimal);
	printf("ex.hex: %"PRIu64"\n", ex->hex);
}


void stack_example() {
	struct example ex_on_stack;

	// since ex_on_stack is a stack allocated variable,
	// we use the '.' syntax to access a field in the struct
	ex_on_stack.decimal = 11;
	ex_on_stack.hex = 0xb;

	// to call print_example, we need to pass a pointer. We can use
	// the '&' operator to get the address of any variable.
	print_example(&ex_on_stack);

}

void heap_example() {
	int i; // loop index
	int n = 5; // number of struct examples to allocate in memory

	// ex_on_heap is a pointer to a location in memory where
	// a struct example is stored
	struct example *ex_on_heap;
	
	// We use malloc to allocate memory for n=5 struct examples.
	// The memory is contiguous, so each struct example directly
	// follows the previous.
	// We could also treat them as an array if we would like,
	// where &ex_on_heap[0] is the address ex_on_heap
	ex_on_heap = malloc(sizeof(*ex_on_heap) * n);
	if (ex_on_heap == NULL) {
		printf("error allocating memory for ex_array\n");
		return;
	}

	for (i = 0; i < n; i++) {
		// since ex_on_heap is a pointer, we use the '->' syntax
		// to access a field in the struct.
		(ex_on_heap + i)->decimal = i;

		// we could also treat ex_on_heap as an array, and use the []
		// syntax. This dereferences the pointer, so we then need to
		// use the '.' syntax to access a field.
		ex_on_heap[i].hex = i;
	}

	print_example(ex_on_heap);
	print_example(ex_on_heap + 1);
	print_example(ex_on_heap + (n-1));

	// See page 93 of K&R for the relationship between pointers and arrays!
	printf("&ex_on_heap[0] == ex_on_heap? %d\n",
	       &ex_on_heap[0] == ex_on_heap);

	// since we are done with the memory we malloc-ed, we need to free it
	// to avoid a "memory leak"
	free(ex_on_heap);
}


// an example that reads up to 5 lines of a file to populate
// an array of struct examles
void file_reading_example() {
	FILE *fp; // fp is often used for "file pointer"
	int max_str_size = 80;
	char str[max_str_size]; // a buffer of characters to read data into
	
	int i; // loop index
	int n = 5; // number of struct examples to allocate in memory

	// allocated memory for our example structs
	struct example *ex_on_heap;
	ex_on_heap = malloc(sizeof(*ex_on_heap) * n);
	if (ex_on_heap == NULL) {
		printf("error allocating memory for ex_array\n");
		return;
	}

	// open the file data.txt in read-only mode
	fp = fopen("data.txt", "r");
	if (fp == NULL) {
		perror("file open failed");
		return;
	}

	
	for (i = 0; i < n; i++) {
		// fgets reads up to max_str_size characters from fp
		// and stores the result in str.
		// See `man fgets` for details
		if (fgets(str, max_str_size, fp) != NULL) {
			// sscanf parses the input string 'str' according
			// to the format string. We again use the values
			// defined in inttypes.h to read a decimal val into a
			// uint64_t and a hexadecimal val into a uin64_t
			// See `man sscanf` for more details.
			sscanf(str, "%"PRIu64" %"SCNx64,
			       &ex_on_heap[i].decimal, &ex_on_heap[i].hex);
			print_example(&ex_on_heap[i]);
		}
	}
	fclose(fp);
}

int main(int argc, char **argv) {
	stack_example();
	heap_example();

	file_reading_example();
}
