/************************* 
 * Owen Sullivan
 * CPSC 2310-002
 * opsulli@clemson.edu
*************************/

#include "functions.h"

header_t readHeader(FILE *inFile) {

    buffer_t buffer;
    
    // Read the first line (until a new line) and store as type buffer
    fscanf(inFile, " %[^ ,^\n]*c", buffer.type);

    // If the first character in the width buffer is not a digit, read
    // the next line (assuming last read line is entirely a comment)
    while (!isdigit(buffer.width[0])) {

        fscanf(inFile, " %[^ ,^\n]*c", buffer.width);

    }

    // If the first character in the height buffer is not a digit, read
    // the next line (assuming last read line is entirely a comment)
    while (!isdigit(buffer.height[0])) {

        fscanf(inFile, " %[^ ,^\n]*c", buffer.height);

    }

    // If the first character in the maxColor buffer is not a digit, read
    // the next line (assuming last read line is entirely a comment)
    while (!isdigit(buffer.maxColor[0])) {

        fscanf(inFile, " %[^ ,^\n]*c", buffer.maxColor);

    }
    
    // Create a header_t object and set it to the return value
    // of convertHeader
    header_t header = convertHeader(buffer);

    // Read the remaining characters up till a new line, in case there is 
    // a bad comment preventing the pixel data from being read
    char remaining[100];
    fscanf(inFile, "%[^\n]*c", remaining);

    return header;

}

void writeHeader(FILE *outFile, header_t header) {

    // Print each value in header to the output file
    fprintf(outFile, "%s\n", header.type);
    fprintf(outFile, "%d\n", header.width);
    fprintf(outFile, "%d\n", header.height);
    fprintf(outFile, "%d\n", header.maxColor);

}

header_t convertHeader(buffer_t buffer) {

    header_t header;

    // Copy the type buffer to the header type
    strcpy(header.type, buffer.type);

    // Set the header width to the converted integer of the
    // width buffer
    header.width = atoi(buffer.width);

    // Set the header width to the converted integer of the
    // height buffer
    header.height = atoi(buffer.height);

    // Set the header width to the converted integer of the
    // maxColor buffer
    header.maxColor = atoi(buffer.maxColor);

    return header;

}

pixel_t **allocateMemory(int width, int height) {

    // Create a double pointer
    pixel_t **ptr = malloc(height * sizeof(pixel_t *));

    for (int i = 0; i < height; i++) {

        // Allocate memory for each single pointer index
        ptr[i] = malloc(width * sizeof(pixel_t));

    }

    return ptr;

}

void freeMemory(pixel_t **ptr, int height) {

    for (int i = 0; i < height; i++) {

        // Free each single pointer index
        free(ptr[i]);

    }

    // Free the main double pointer
    free(ptr);

}

void readImage(FILE *inFile, pixel_t **image, int width, int height) {

    // Move file pointer forward by one character to account for
    // new line after maxColor header value
    fseek(inFile, 1, SEEK_CUR);

    // Loop for every row of pixels
    for (int i = 0; i < height; i++) {

        // Loop for every column of pixels
        for (int j = 0; j < width; j++) {

            // Read r, g, and b value as an unsigned char to the next index
            // in the pixel pointer array
            fread(&image[i][j].r, sizeof(unsigned char), 1, inFile);
            fread(&image[i][j].g, sizeof(unsigned char), 1, inFile);
            fread(&image[i][j].b, sizeof(unsigned char), 1, inFile);

        }

    }

}

void writeImage(FILE *outFile, pixel_t **image, int width, int height) {

    // Loop for every row of pixels
    for (int i = 0; i < height; i++) {

        // Loop for every column of pixels
        for (int j = 0; j < width; j++) {

            // Print each r, g, and b value to the output file
            fprintf(outFile, "%c", image[i][j].r);
            fprintf(outFile, "%c", image[i][j].g);
            fprintf(outFile, "%c", image[i][j].b);

        }

    }

}

void getResizedPixels(pixel_t **original, pixel_t **resized,
                      int width, int height, double rWidth, double rHeight) {

    // Loop for every row of pixels
    for (int i = 0; i < height; i++) {

        // Loop for every column of pixels
        for (int j = 0; j < width; j++) {

            // Set the next pixel in the resized image to the pixel
            // at the relative position from the original image
            resized[i][j] = original[(int)(i * rHeight)][(int)(j * rWidth)];

        }

    }

}

void resizeImage(FILE *inFile, FILE *outFile) {

    // Get the header from the input image
    header_t originalHeader = readHeader(inFile);
    
    // Create and allocate a pointer for the original image's pixels
    pixel_t **originalImage = allocateMemory(originalHeader.width,
                                             originalHeader.height);

    readImage(inFile, originalImage,
                      originalHeader.width, originalHeader.height);

    int widthTransformation;
    int heightTransformation;
    // Ask the user how they'd like to transform the image
    fprintf(stdout, "How much would you like to resize the image by? ");
    fscanf(stdin, " %d %d", &widthTransformation, &heightTransformation);
    // Check that the user's transformation does not result in negative
    // width or height
    while ((originalHeader.width + widthTransformation) <= 0 ||
           (originalHeader.height + heightTransformation) <= 0) {

        fprintf(stdout,
                "The transformation results in a negative value. Try again: ");
        fscanf(stdin, " %d %d",
               &widthTransformation, &heightTransformation);

    }

    // Determine the relative scales for the width and height
    double relativeWidth = ((double)originalHeader.width /
                            (originalHeader.width + widthTransformation));
    double relativeHeight = ((double)originalHeader.height /
                             (originalHeader.height + heightTransformation));

    // Create and write a new header for the resized image, with the new
    // width and height values
    header_t resizedHeader = originalHeader;
    resizedHeader.width = (originalHeader.width + widthTransformation);
    resizedHeader.height = (originalHeader.height + heightTransformation);
    writeHeader(outFile, resizedHeader);

    // Create and allocate a pointer for the resized image's pixels
    pixel_t **resizedImage = allocateMemory(resizedHeader.width,
                                            resizedHeader.height);

    // Get the scaled pixels for the resized image
    getResizedPixels(originalImage, resizedImage,
                     resizedHeader.width, resizedHeader.height,
                     relativeWidth, relativeHeight);

    // Write the resized image to the output file
    writeImage(outFile, resizedImage,
               resizedHeader.width, resizedHeader.height);

    // Free both pointers
    freeMemory(originalImage, originalHeader.height);
    freeMemory(resizedImage, resizedHeader.height);

}

unsigned char getNegativeValue(unsigned char value) {

    // Invert the pixel value
    return (255 - value);

}

void getNegativePixels(pixel_t **original, pixel_t **negative,
                       int width, int height) {

    // Loop through every row of pixels
    for (int i = 0; i < height; i++) {

        // Loop through every column of pixels
        for (int j = 0; j < width; j++) {

            // Calculate the negative of each r, g, and b value
            negative[i][j].r = getNegativeValue(original[i][j].r);
            negative[i][j].g = getNegativeValue(original[i][j].g);
            negative[i][j].b = getNegativeValue(original[i][j].b);

        }

    }

}

void negativeImage(FILE *inFile, FILE *outFile) {
    
    // Get the header from the input image
    header_t header = readHeader(inFile);

    // Write the header, since there are no changes being made to the header
    writeHeader(outFile, header);
    
    // Create and allocate a pointer for the original image's pixels
    pixel_t **originalImage = allocateMemory(header.width, header.height);

    readImage(inFile, originalImage, header.width, header.height);

    // Create and allocate a pointer for the negative image's pixels
    pixel_t **negativeImage = allocateMemory(header.width, header.height);

    // Get the negative pixel values
    getNegativePixels(originalImage, negativeImage,
                      header.width, header.height);

    // Write the negative image to the output file
    writeImage(outFile, negativeImage, header.width, header.height);

    // Free both pointers
    freeMemory(originalImage, header.height);
    freeMemory(negativeImage, header.height);

}