text: #undef _GNU_SOURCE // gets rid of vim warning
  #define _GNU_SOURCE
  #define PRECISION_8 255
  #define PRECISION_16 65535
  
  #include "io.h"
  #include "util.h"
  #include <math.h>
  #include <stdio.h> 
  #include <stdint.h>
  #include <unistd.h>
  #include <stdlib.h>
  #include <ctype.h>
  #include <string.h>
  
  /*
   * Parses command line arguments.
   */
  int parse_args(
  		int argc,
  	   	char *argv[],
  	   	char *filepath,
  	   	char *outputfilepath,
  	   	sim_params *params,
  		bool *ascii_encoding
  ) {
  	int c;
  	while((c = getopt(argc, argv, "af:o:n:t:r:e:c:g:v:s:d:m:")) != -1){
  		switch(c){
  			case 'f':	
  				strncpy(filepath, optarg, FILEPATH_MAXLEN);
  				break;
  			case 'o':
  				strncpy(outputfilepath, optarg, FILEPATH_MAXLEN);
  				break;
  			case 'a':
  				*ascii_encoding = true;
  				break;
  			case 't': params->ttl = atoi(optarg); break;
  			case 'n': params->n = atoi(optarg); break;
  			case 'r': params->p_radius = atoi(optarg); break;
  			case 'e': params->p_enertia = atof(optarg); break;
  			case 'c': params->p_capacity = atof(optarg); break;
  			case 'g': params->p_gravity = atof(optarg); break;
  			case 'v': params->p_evaporation = atof(optarg); break;
  			case 's': params->p_erosion = atof(optarg); break;
  			case 'd': params->p_deposition = atof(optarg); break;
  			case 'm': params->p_min_slope = atof(optarg); break;
  			default:
  				fprintf(stderr, "Usage: %s\n", argv[0]);
  				return 1;
  		}	
  	}
  
  	return 0;
  }
  
  /*
   * gets next value from *.pgm file.
   */
  int pgm_next_value(FILE *fp, char *buffer, size_t size) {
  	int c = '\0';	
  	size_t i = 1;
  
  	while(c != EOF){
  		// skip spaces
  		while((c = fgetc(fp)) != EOF && isspace(c));
  
  		// skip commented lines	
  		if(c == '#'){
  			while(c != '\r' && c != '\n' && (c = fgetc(fp)) != EOF);
  		} else if (c != EOF){
  			// read values to buffer
  			buffer[0] = c;
  			for(; i < size && (c = fgetc(fp)) != EOF && !isspace(c); i++) {
  				buffer[i] = c;	
  			}
  
  			// null terminate
  			buffer[min(size - 1, i)] = '\0';
  			return i;
  		}
  	}
  
  	return EOF;	
  }
  
  /*
   * Loads *.pgm into image `img`. `img` contains an internal buffer which is
   * dynamically allocated in load_pgm and should be free'd after use.
   */
  int load_pgm(
  		const char *filepath,
  		image *img
  ) {
  	FILE	*fp = fopen(filepath, "r");
  	char	*line = NULL;
  	char	magic[16];
  	char	value_buffer[16];
  	int		precision;
  
  	if(fp == NULL)
  		return 1;
  
  	// read header
  	if(pgm_next_value(fp, value_buffer, 16) == EOF) return 1;
  	strncpy(magic, value_buffer, 16);
  	if(pgm_next_value(fp, value_buffer, 16) == EOF) return 1;
  	img->width = atoi(value_buffer);
  	if(pgm_next_value(fp, value_buffer, 16) == EOF) return 1;
  	img->height = atoi(value_buffer);
  	if(pgm_next_value(fp, value_buffer, 16) == EOF) return 1;
  	precision = atoi(value_buffer);
  
  	// Allocate buffer for pixel values
  	img->buffer = malloc(sizeof(double) * (img->height) * (img->width));
  	double *buffer = (double *) img->buffer;
  	if(buffer == NULL)
  		return 1;
  
  	// Read pixel values to buffer. 
  	// If magic is "P2" then values are ASCII encoded. 
  	// If magic is "P5" then values are binary encoded. 
  	if(strncmp(magic, "P2", 2) == 0){
  		for(int i = 0; pgm_next_value(fp, value_buffer, 16) != EOF; i++)
  			buffer[i] = atof(value_buffer) / precision;
  	} else if(strncmp(magic, "P5", 2) == 0) {
  		int byte_depth = precision <= PRECISION_8 ? 1 : 2; 
  		char data[byte_depth];
  		for(int i = 0; i < img->width * img->height; i++) {
  			fread(data, sizeof(char), byte_depth, fp);
  			int val = (byte_depth == 2) ? 
  				((data[0] << 8) & 0xFF00) | (data[1] & 0x00FF) :
  				data[0] & 0xFF;
  			buffer[i] = (double) val / precision;
  		}
  	}
  
  	// cleanup
  	fclose(fp);
  	if(line)
  		free(line);
  
  	return 0;
  }
  
  /*
   * Saves image `img` to a *.pgm file.
   */
  int save_pgm(
  		const char *filepath,
  	   	image *img,
  		bool ascii_encoding
  ) {
  	FILE *fp = fopen(filepath, "w");
  
  	// write "header"
  	fputs((ascii_encoding) ? "P2\n" : "P5\n", fp);	
  	fputs("# Generated by erodr\n", fp);
  	fprintf(fp, "%d %d\n", img->width, img->height);	
  	fprintf(fp, "%d\n", PRECISION_16);	
  	
  	// write data.
  	double *buffer = (double *)img->buffer;
  	if (ascii_encoding) {	
  		for(int i = 0; i < img->width * img->height; i++) {
  			fprintf(fp, "%d\n", (int)round(buffer[i]*PRECISION_16));	
  		}
  	} else {
  		char *data = (char *) malloc(sizeof(uint16_t) * img->width * img->height);
  		for(int i = 0; i < img->width * img->height; i++) {
  			int gv = (uint16_t)round(buffer[i]*PRECISION_16);
  			data[2 * i]	= (gv >> 8) & 0xFF;
  			data[2 * i + 1]	= gv & 0xFF;
  		}
  		fwrite(data, sizeof(uint16_t), img->width * img->height, fp); 
  		free(data);
  	}	
  	fclose(fp);
  	
  	return 0;
  }
  
  