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

#include <jpeglib.h>
#include <argtable2.h>
#include <string.h>

const char * input_file;
const char * output_file;
const char * filter;
const char * axis;
const char * direction;

double times;

/* we will be using this uninitialized pointer later to store raw, uncompressd image */
JSAMPARRAY row_pointers = NULL;

/* dimensions of the image we want to write */
JDIMENSION width;
JDIMENSION height;

int num_components;
int quality = 75;

J_COLOR_SPACE color_space;


/* ========================================= */
/* FLIP */
/* ========================================= */
void flip(){

    int x, y;

    if (color_space != JCS_RGB)
        return;

    /* odbicie poziome */
    if(strcmp(axis, "y") == 0){

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

    /* odbicie pionowe */
    else if(strcmp(axis, "x") == 0){

        for(y = 0; y < height / 2; y++){

            JSAMPROW tmp = row_pointers[y];

            row_pointers[y] = row_pointers[height - 1 - y];

            row_pointers[height - 1 - y] = tmp;
        }
    }
}


/* ========================================= */
/* ROTATE */
/* ========================================= */
void rotate() {

    int x, y;

    if (color_space != JCS_RGB)
        return;

    JDIMENSION new_width = height;
    JDIMENSION new_height = width;

    JSAMPARRAY new_rows =
        (JSAMPARRAY) malloc(sizeof(JSAMPROW) * new_height);

    for (y = 0; y < new_height; y++) {
        new_rows[y] = (JSAMPROW) malloc(new_width * 3);
    }

    for (y = 0; y < height; y++) {

        for (x = 0; x < width; x++) {

            JSAMPROW old_row = row_pointers[y];
            JSAMPROW old_pixel = &(old_row[x * 3]);

            JSAMPROW new_row;
            JSAMPROW new_pixel;

            /* ROTATE RIGHT */
            if (strcmp(direction, "right") == 0) {

                new_row = new_rows[x];
                new_pixel = &(new_row[(height - 1 - y) * 3]);
            }

            /* ROTATE LEFT */
            else {

                new_row = new_rows[width - 1 - x];
                new_pixel = &(new_row[y * 3]);
            }

            new_pixel[0] = old_pixel[0];
            new_pixel[1] = old_pixel[1];
            new_pixel[2] = old_pixel[2];
        }
    }

    for (y = 0; y < height; y++) {
        free(row_pointers[y]);
    }

    free(row_pointers);

    row_pointers = new_rows;

    width = new_width;
    height = new_height;
}


/* ========================================= */
/* PROCESS */
/* ========================================= */
void process_file(){

    if(strcmp(filter, "flip") == 0){
        flip();
    }

    else if(strcmp(filter, "rotate") == 0){
        rotate();
    }
}


void abort_(const char * s, ...)
{
    va_list args;

    va_start(args, s);

    vfprintf(stderr, s, args);

    fprintf(stderr, "\n");

    va_end(args);

    abort();
}


/* ========================================= */
/* READ JPEG */
/* ========================================= */
void read_jpeg_file(const char *filename)
{
    struct jpeg_decompress_struct cinfo;
    struct jpeg_error_mgr jerr;

    int y;

    FILE *infile = fopen(filename, "rb");

    if (!infile)
    {
        abort_("Error opening input jpeg file %s!\n", 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;

    size_t rowbytes = width * num_components;

    row_pointers =
        (JSAMPARRAY) malloc(sizeof(j_common_ptr) * height);

    for (y = 0; y < height; y++){
        row_pointers[y] = (JSAMPROW) malloc(rowbytes);
    }

    y = 0;

    JSAMPARRAY tmp = row_pointers;

    while(cinfo.output_scanline < cinfo.image_height)
    {
        y = jpeg_read_scanlines(&cinfo, tmp, 1);

        tmp += y;
    }

    jpeg_finish_decompress(&cinfo);

    jpeg_destroy_decompress(&cinfo);

    fclose(infile);
}


/* ========================================= */
/* WRITE JPEG */
/* ========================================= */
void write_jpeg_file(const char *filename)
{
    struct jpeg_compress_struct cinfo;
    struct jpeg_error_mgr jerr;

    int y;

    JSAMPARRAY tmp;

    FILE *outfile = fopen(filename, "wb");

    if (!outfile) {
        abort_("Error opening output jpeg file %s!\n", 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 = num_components;
    cinfo.in_color_space = color_space;

    jpeg_set_defaults(&cinfo);

    jpeg_set_quality(&cinfo, quality, TRUE);

    jpeg_start_compress(&cinfo, TRUE);

    tmp = row_pointers;

    while(cinfo.next_scanline < cinfo.image_height)
    {
        y = jpeg_write_scanlines(&cinfo, tmp, 1);

        tmp += y;
    }

    jpeg_finish_compress(&cinfo);

    jpeg_destroy_compress(&cinfo);

    fclose(outfile);

    for (y = 0; y < height; y++){
        free(row_pointers[y]);
    }

    free(row_pointers);
}


/* ========================================= */
/* MAIN */
/* ========================================= */
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");

    struct arg_str *axis_arg =
        arg_str0("a", "axis", "<axis>", "flip axis");

    struct arg_str *direction_arg =
        arg_str0("d", "direction", "<direction>", "rotate direction");

    struct arg_dbl *times_arg =
        arg_dbl0("t", "times", "<times>", "Multiplyer");

    struct arg_lit *help =
        arg_lit0("h", "help", "print this help and exit");

    struct arg_end *end = arg_end(10);

    int nerrors;

    void *argtable[] = {
        input_file_arg,
        output_file_arg,
        filter_arg,
        times_arg,
        axis_arg,
        direction_arg,
        help,
        end
    };

    if (arg_nullcheck(argtable) != 0)
        printf("error: insufficient memory\n");

    times_arg->dval[0] = 1;

    axis_arg->sval[0] = "y";

    direction_arg->sval[0] = "right";

    nerrors = arg_parse(argc, argv, argtable);

    if (help->count > 0){

        printf("Usage: geometry");

        arg_print_syntax(stdout, argtable,"\n");

        arg_print_glossary(stdout, argtable,
                           "  %-25s %s\n");

        arg_freetable(argtable,
                      sizeof(argtable)/sizeof(argtable[0]));

        return 0;
    }

    if (nerrors == 0){

        input_file = input_file_arg->filename[0];

        output_file = output_file_arg->filename[0];

        filter = filter_arg->sval[0];

        times = times_arg->dval[0];

        axis = axis_arg->sval[0];

        direction = direction_arg->sval[0];
    }
    else{

        arg_print_errors(stderr, end, "point");

        arg_print_glossary(stderr, argtable,
                           " %-25s %s\n");

        arg_freetable(argtable,
                      sizeof(argtable)/sizeof(argtable[0]));

        return 1;
    }

    read_jpeg_file(input_file);

    process_file();

    write_jpeg_file(output_file);

    arg_freetable(argtable,
                  sizeof(argtable)/sizeof(argtable[0]));

    return 0;
}