#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
 
#include <jpeglib.h>
#include <argtable2.h>
 
/* Globalne zmienne */
const char *input_file;
const char *output_file;
const char *filter;
const char *axis = NULL;
const char *direction = NULL;
 
JSAMPARRAY row_pointers = NULL;
JDIMENSION width;
JDIMENSION height;
int num_components;
int quality = 75;
J_COLOR_SPACE color_space;
 
void abort_(const char * s, ...) {
    va_list args;
    va_start(args, s);
    vfprintf(stderr, s, args);
    fprintf(stderr, "\n");
    va_end(args);
    exit(1);
}
 
 
void flip(const char* a) {
    int x, y;
    if (color_space != JCS_RGB) return;
 
    if (strcmp(a, "y") == 0) {
        printf("Performing flip along axis: y\n");
        for (y = 0; y < height; y++) {
            JSAMPROW row = row_pointers[y];
            for (x = 0; x < width / 2; x++) {
                JSAMPROW ptr1 = &(row[x * 3]);
                JSAMPROW ptr2 = &(row[(width - 1 - x) * 3]);
 
                JSAMPLE tmp;
                tmp = ptr1[0]; ptr1[0] = ptr2[0]; ptr2[0] = tmp;
                tmp = ptr1[1]; ptr1[1] = ptr2[1]; ptr2[1] = tmp;
                tmp = ptr1[2]; ptr1[2] = ptr2[2]; ptr2[2] = tmp;
            }
        }
    } else if (strcmp(a, "x") == 0) {
        printf("Performing flip along axis: x\n");
        for (y = 0; y < height / 2; y++) {
            JSAMPROW tmp_row = row_pointers[y];
            row_pointers[y] = row_pointers[height - 1 - y];
            row_pointers[height - 1 - y] = tmp_row;
        }
    }
}
 
void rotate(const char* d) {
    if (color_space != JCS_RGB) return;
    printf("Performing rotation: %s\n", d);
 
    JDIMENSION new_width = height;
    JDIMENSION new_height = width;
 
    JSAMPARRAY new_row_pointers = (JSAMPARRAY) malloc(sizeof(JSAMPROW) * new_height);
    for (int y = 0; y < new_height; y++) {
        new_row_pointers[y] = (JSAMPROW) malloc(new_width * 3);
    }
 
    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            int new_x, new_y;
            if (strcmp(d, "left") == 0) {
                new_x = y;
                new_y = width - 1 - x;
            } else {
                new_x = height - 1 - y;
                new_y = x;
            }
 
            JSAMPROW src_ptr = &(row_pointers[y][x * 3]);
            JSAMPROW dst_ptr = &(new_row_pointers[new_y][new_x * 3]);
 
            dst_ptr[0] = src_ptr[0];
            dst_ptr[1] = src_ptr[1];
            dst_ptr[2] = src_ptr[2];
        }
    }
 
    for (int y = 0; y < height; y++) free(row_pointers[y]);
    free(row_pointers);
 
    row_pointers = new_row_pointers;
    width = new_width;
    height = new_height;
}
 
 
void read_jpeg_file(const char *filename) {
    struct jpeg_decompress_struct cinfo;
    struct jpeg_error_mgr jerr;
    FILE *infile = fopen(filename, "rb");
    if (!infile) abort_("Error: Cannot open %s", filename);
    cinfo.err = jpeg_std_error(&jerr);
    jpeg_create_decompress(&cinfo);
    jpeg_stdio_src(&cinfo, infile);
    jpeg_read_header(&cinfo, TRUE);
    jpeg_start_decompress(&cinfo);
    width = cinfo.output_width;
    height = cinfo.output_height;
    num_components = cinfo.out_color_components;
    color_space = cinfo.out_color_space;
    row_pointers = (JSAMPARRAY) malloc(sizeof(JSAMPROW) * height);
    for (int y = 0; y < height; y++)
        row_pointers[y] = (JSAMPROW) malloc(width * num_components);
    while (cinfo.output_scanline < cinfo.image_height)
        jpeg_read_scanlines(&cinfo, &row_pointers[cinfo.output_scanline], 1);
    jpeg_finish_decompress(&cinfo);
    jpeg_destroy_decompress(&cinfo);
    fclose(infile);
}
 
void write_jpeg_file(const char *filename) {
    struct jpeg_compress_struct cinfo;
    struct jpeg_error_mgr jerr;
    FILE *outfile = fopen(filename, "wb");
    if (!outfile) abort_("Error: Cannot write to %s", filename);
    cinfo.err = jpeg_std_error(&jerr);
    jpeg_create_compress(&cinfo);
    jpeg_stdio_dest(&cinfo, outfile);
    cinfo.image_width = width;
    cinfo.image_height = height;
    cinfo.input_components = 3;
    cinfo.in_color_space = JCS_RGB;
    jpeg_set_defaults(&cinfo);
    jpeg_set_quality(&cinfo, quality, TRUE);
    jpeg_start_compress(&cinfo, TRUE);
    while (cinfo.next_scanline < cinfo.image_height)
        jpeg_write_scanlines(&cinfo, &row_pointers[cinfo.next_scanline], 1);
    jpeg_finish_compress(&cinfo);
    jpeg_destroy_compress(&cinfo);
    fclose(outfile);
    for (int y = 0; y < height; y++) free(row_pointers[y]);
    free(row_pointers);
}
 
void process_file() {
    if (strcmp(filter, "flip") == 0) {
        if (axis) flip(axis);
        else abort_("Error: --axis required");
    } else if (strcmp(filter, "rotate") == 0) {
        if (direction) rotate(direction);
        else abort_("Error: --direction required");
    }
}
 
int main(int argc, char **argv) {
    struct arg_file *input_file_arg  = arg_file1("i", "in-file", "<input>", "Input JPEG File");
    struct arg_file *output_file_arg = arg_file1("o", "out-file", "<output>", "Output JPEG File");
    struct arg_str  *filter_arg      = arg_str1("f", "filter", "<filter>", "Filter: flip | rotate");
    struct arg_str  *axis_arg        = arg_str0("a", "axis", "<a>", "Flip axis: x | y");
    struct arg_str  *direction_arg   = arg_str0("d", "direction", "<d>", "Rotate direction: left | right");
    struct arg_lit  *help            = arg_lit0("h", "help", "Print help");
    struct arg_end  *end             = arg_end(20);
 
    void *argtable[] = {input_file_arg, output_file_arg, filter_arg, axis_arg, direction_arg, help, end};
    if (arg_parse(argc, argv, argtable) > 0 || help->count > 0) {
        arg_print_errors(stderr, end, "geometry");
        arg_print_glossary(stdout, argtable, "  %-25s %s\n");
        return 1;
    }
 
    input_file = input_file_arg->filename[0];
    output_file = output_file_arg->filename[0];
    filter = filter_arg->sval[0];
    if (axis_arg->count > 0) axis = axis_arg->sval[0];
    if (direction_arg->count > 0) direction = direction_arg->sval[0];
 
    read_jpeg_file(input_file);
    process_file();
    write_jpeg_file(output_file);
 
    arg_freetable(argtable, sizeof(argtable)/sizeof(argtable[0]));
    return 0;
}