#include <stdio.h>
#include <string.h>
#include <stdlib.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;

FILE *safe_fopen(char *, char *);
void *safe_alloc(int, size_t);
unsigned examine_header(FILE *, unsigned char *);
void read_bitmap(FILE *, PIXEL *, unsigned);
void create_filename(char *, char *, char *, char *);
void to_grayscale(PIXEL *, unsigned);
void to_one_panel(PIXEL *, unsigned, unsigned);
unsigned char gen_mask(unsigned);
void write_bitmap(FILE *, unsigned char *, PIXEL *, unsigned);

int main(int argc, char *argv[]) {
	FILE *pin_bmp, *pres_bmp;

    if(argc != 4) {
        puts("Example call: ./a.out in.bmp plane 1");
        exit(EXIT_FAILURE);
    }

    pin_bmp = safe_fopen(argv[1], "rb");
    unsigned char *header = safe_alloc(HEADER_SIZE, sizeof(unsigned char));
    unsigned size = examine_header(pin_bmp, header);

    PIXEL *pixels = safe_alloc(size, sizeof(PIXEL));
    read_bitmap(pin_bmp, pixels, size);
    fclose(pin_bmp);
    
    if(strcmp(argv[2], "plane") == 0) {
        to_grayscale(pixels, size);
        unsigned position = atoi(argv[3]);
        if(position < 1 || position > 8) {
        	puts("Position must be in this range: [1, 8].");
        	exit(EXIT_FAILURE);
        }
        to_one_panel(pixels, size, 8 - position);
    } else {
        puts("Unrecognized option.");
        puts("Choose this one only: plane");

        exit(EXIT_FAILURE);
    }
    
    char *output_filename = safe_alloc(strlen(argv[1]) + strlen(argv[2]) + 4, sizeof(char));
    create_filename(output_filename, argv[1], argv[2], argv[3]);
    
    pres_bmp = safe_fopen(output_filename, "wb");
    write_bitmap(pres_bmp, header, pixels, size);
    fclose(pres_bmp);
	
	free(output_filename);
	free(pixels);
	free(header);

    return EXIT_SUCCESS;
    
	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 *safe_alloc(int size, size_t byte_size) {
	void *p = calloc(size, byte_size);
	
	if(p == NULL) {
		puts("Not enough memory!");
		exit(EXIT_FAILURE);
	}
	
	return p;
}

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, PIXEL *pixels, unsigned size) {
    fread(pixels, sizeof(PIXEL), size, pin_bmp);
}

void to_grayscale(PIXEL *pixels, unsigned size) {
    unsigned i;
    unsigned char avg;

    for(i = 0;i < size;i++) {
        avg = (pixels[i].R + pixels[i].G + pixels[i].B) / 3;

        pixels[i].R = avg;
        pixels[i].G = avg;
        pixels[i].B = avg;
    }
}

void to_one_panel(PIXEL *pixels, unsigned size, unsigned position) {
	unsigned i;
	unsigned char mask = gen_mask(position);
	
	for(i = 0;i < size;i++) {
        pixels[i].R &= mask;
        pixels[i].G &= mask;
        pixels[i].B &= mask;
    }
}

unsigned char gen_mask(unsigned position) {
	return 1 << position;
}

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

void write_bitmap(FILE *pres_bmp, unsigned char *header, PIXEL *pixels, unsigned
size) {
    fwrite(header, sizeof(unsigned char), HEADER_SIZE, pres_bmp);
    fwrite(pixels, sizeof(PIXEL), size, pres_bmp);
}
