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

#define HEADER_SIZE 54
#define WIDTH_POS 18
#define HEIGHT_POS 22

typedef struct pixel_st {
	unsigned char B;
	unsigned char G;
	unsigned char R;
} PIXEL;

typedef struct node_st {
	PIXEL data;
	struct node_st *pnext;
} NODE;

FILE *safe_fopen(char *, char *);

void init_list(NODE **, NODE **);
NODE *create_node(PIXEL);
void add_on_beginning(NODE **, NODE **, PIXEL);
void add_on_end(NODE **, NODE **, PIXEL);
void delete_list(NODE **, NODE **);


unsigned examine_header(FILE *, unsigned char *);
void read_bitmap(FILE *, NODE **, NODE **, char *);
void write_bitmap(FILE *, unsigned char *, NODE *);

void create_filename(char *, char *, char *, char *);
void to_grayscale(NODE *);

unsigned size;

int main (int argc, char **argv) {
	
	if(argc != 4) {
        puts("Example call: ./a.out in.bmp grayscale flipped");
        exit(EXIT_FAILURE);
    }
	
	NODE *head, *tail;
	unsigned char *header;
	FILE *pin_bmp, *pres_bmp;
	
	pin_bmp = safe_fopen(argv[1], "rb");
	header = calloc(HEADER_SIZE, sizeof(unsigned char));
	size = examine_header(pin_bmp, header);
	
	init_list(&head, &tail);
    read_bitmap(pin_bmp, &head, &tail, argv[3]);
    fclose(pin_bmp);
    
    if(strcmp(argv[2], "grayscale") == 0) {
        to_grayscale(head);
    } else {
        puts("Unrecognized option.");
        puts("Choose this one only: grayscale");

        exit(EXIT_FAILURE);
    }
    
    char *output_filename = calloc(strlen(argv[1]) + strlen(argv[2]) + strlen(argv[3]) + 3, sizeof(char));
    create_filename(output_filename, argv[1], argv[2], argv[3]);
    
    pres_bmp = safe_fopen(output_filename, "wb");
    write_bitmap(pres_bmp, header, head);
    fclose(pres_bmp);
	
	free(output_filename);
	free(header);
	delete_list(&head, &tail);
	
	return 0;
}

FILE *safe_fopen(char *name, char *mode) {
    FILE *pf = fopen(name, mode);

    if(pf == NULL) {
        printf("File %s could not be opened!\n", name);
        exit(EXIT_FAILURE);
    }

    return pf;
}

void init_list(NODE **phead, NODE **ptail) {
	*phead = NULL;
	*ptail = NULL;
}

NODE *create_node(PIXEL p) {
	NODE *pnew = malloc(sizeof(NODE));
	if(pnew == NULL) {
		printf("Not enough memory!\n");
        exit(EXIT_FAILURE);
	}
	pnew -> data = p;
	pnew -> pnext = NULL;
	
	return pnew;
}

void add_on_beginning(NODE **phead, NODE **ptail, PIXEL p) {
	NODE *pnew = create_node(p);
	
	if(*phead != NULL) {
		pnew -> pnext = *phead;
	} else {
		*ptail = pnew;
	}
	*phead = pnew;
}

void add_on_end(NODE **phead, NODE **ptail, PIXEL p) {
	NODE *pnew = create_node(p);
	
	if (*phead != NULL) { 
		(*ptail) -> pnext = pnew;
	} else {
		*phead = pnew;
	}
	*ptail = pnew;
}


void delete_list(NODE **phead, NODE **ptail) {
	NODE *ptemp = *phead;
	
	while(*phead != NULL) {
		ptemp = *phead;
		*phead = (*phead) -> pnext;
		
		ptemp -> pnext = NULL;
		free(ptemp);
	}
	*ptail = NULL;
}

unsigned examine_header(FILE *pin, unsigned char *header) {
    fread(header, sizeof(unsigned char), HEADER_SIZE, pin);

    unsigned width = *(unsigned *) &header[WIDTH_POS];
    unsigned height = *(unsigned *) &header[HEIGHT_POS];

    return width * height;
}

void read_bitmap(FILE *pin_bmp, NODE **phead, NODE **ptail, char *regime) {
    PIXEL p;

    	if(strcmp(regime, "normal") == 0)
    		while (fread(&p, sizeof(PIXEL), 1, pin_bmp) != 0)
    	    	add_on_end(phead, ptail, p);
		else if(strcmp(regime, "flipped") == 0)
		    while (fread(&p, sizeof(PIXEL), 1, pin_bmp) != 0)
    			add_on_beginning(phead, ptail, p);
    	else {
			puts("Unrecognized option.");
        	puts("Choose one of the following: normal, flipped");

        	exit(EXIT_FAILURE);
		}    
	/*int i = 0;	
	while (fread(&p, sizeof(PIXEL), 1, pin_bmp) != 0) {
		if(i < size/2)
			add_on_beginning(phead, ptail, p);
		else
		    add_on_end(phead, ptail, p);
		i++;
	}*/
    
}

void to_grayscale(NODE *head) {
    unsigned i;
    unsigned char avg;

    while(head != NULL) {
        avg = (head->data.R + head->data.G + head->data.B) / 3;

        head->data.R = avg;
        head->data.G = avg;
        head->data.B = avg;
        
        head = head -> pnext;
    }
}

void create_filename(char *output_filename, char *input_filename, char *option1, char *option2) {
	strncpy(output_filename, input_filename, strlen(input_filename) - 4);        // in
    output_filename[strlen(input_filename) - 4] = '\0';
    strcat(output_filename, "_");                                  // in_
    strcat(output_filename, option1);
    strcat(output_filename, "_");                                  // in_
    strcat(output_filename, option2);                                  // in_option
    strcat(output_filename, input_filename + strlen(input_filename) - 4);        // in_option.bmp
}

void write_bitmap(FILE *pres_bmp, unsigned char *header, NODE *head) {
    fwrite(header, sizeof(unsigned char), HEADER_SIZE, pres_bmp);
    while(head != NULL) {
    	fwrite(&(head -> data), sizeof(PIXEL), 1, pres_bmp);
    	head = head -> pnext;
    }
}
