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

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

#define BYTE_SIZE 8
#define BYTE_MASK 0xFF

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 *);
void encrypt(PIXEL *, unsigned, unsigned);
unsigned char get_byte(unsigned, int);
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 encrypt 0xF0F0F0");
        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], "encrypt") == 0) {
        encrypt(pixels, size, (unsigned)strtol(argv[3], NULL, 16));
    } else {
        puts("Unrecognized option.");
        puts("Choose this one only: encrypt");

        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]);
    
    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;
}

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 encrypt(PIXEL *pixels, unsigned size, unsigned key) {
    unsigned i;

    for(i = 0;i < size;i++) {
        pixels[i].R ^= get_byte(key, 2);
        pixels[i].G ^= get_byte(key, 1);
        pixels[i].B ^= get_byte(key, 0);
    }
}

unsigned char get_byte(unsigned num, int position) {
    return (num >> position * BYTE_SIZE) & BYTE_MASK;
}

void create_filename(char *output_filename, char *input_filename, char *option) {
	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, "ed");								//in_optioned
    strcat(output_filename, input_filename + strlen(input_filename) - 4);        // in_option.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);
}
