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

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

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

JSAMPARRAY row_pointers = NULL;

JDIMENSION width;
JDIMENSION height;
int num_components;
int quality = 75;
J_COLOR_SPACE color_space;

void free_image(void)
{
    JDIMENSION y;

    if (row_pointers == NULL) {
        return;
    }

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

    free(row_pointers);
    row_pointers = NULL;
}

void flip_x(void)
{
    JDIMENSION y, x;
    int c;

    for (y = 0; y < height / 2; y++) {
        JDIMENSION opposite_y = height - 1 - y;

        for (x = 0; x < width; x++) {
            JSAMPROW p1 = &row_pointers[y][x * num_components];
            JSAMPROW p2 = &row_pointers[opposite_y][x * num_components];

            for (c = 0; c < num_components; c++) {
                JSAMPLE tmp = p1[c];
                p1[c] = p2[c];
                p2[c] = tmp;
            }
        }
    }
}

void flip_y(void)
{
    JDIMENSION y, x;
    int c;

    for (y = 0; y < height; y++) {
        for (x = 0; x < width / 2; x++) {
            JDIMENSION opposite_x = width - 1 - x;
            JSAMPROW p1 = &row_pointers[y][x * num_components];
            JSAMPROW p2 = &row_pointers[y][opposite_x * num_components];

            for (c = 0; c < num_components; c++) {
                JSAMPLE tmp = p1[c];
                p1[c] = p2[c];
                p2[c] = tmp;
            }
        }
    }
}

void rotate_left(void)
{
    JDIMENSION old_width = width;
    JDIMENSION old_height = height;
    JDIMENSION new_width = old_height;
    JDIMENSION new_height = old_width;
    JDIMENSION y, x;
    int c;

    JSAMPARRAY new_rows = (JSAMPARRAY)malloc(sizeof(JSAMPROW) * new_height);
    if (new_rows == NULL) {
        fprintf(stderr, "Blad alokacji pamieci.\n");
        exit(1);
    }

    for (y = 0; y < new_height; y++) {
        new_rows[y] = (JSAMPROW)malloc((size_t)new_width * (size_t)num_components);
        if (new_rows[y] == NULL) {
            fprintf(stderr, "Blad alokacji pamieci.\n");
            exit(1);
        }
    }

    for (y = 0; y < old_height; y++) {
        for (x = 0; x < old_width; x++) {
            JDIMENSION new_x = y;
            JDIMENSION new_y = old_width - 1 - x;

            for (c = 0; c < num_components; c++) {
                new_rows[new_y][new_x * num_components + c] = row_pointers[y][x * num_components + c];
            }
        }
    }

    free_image();
    row_pointers = new_rows;
    width = new_width;
    height = new_height;
}

void rotate_right(void)
{
    JDIMENSION old_width = width;
    JDIMENSION old_height = height;
    JDIMENSION new_width = old_height;
    JDIMENSION new_height = old_width;
    JDIMENSION y, x;
    int c;

    JSAMPARRAY new_rows = (JSAMPARRAY)malloc(sizeof(JSAMPROW) * new_height);
    if (new_rows == NULL) {
        fprintf(stderr, "Blad alokacji pamieci.\n");
        exit(1);
    }

    for (y = 0; y < new_height; y++) {
        new_rows[y] = (JSAMPROW)malloc((size_t)new_width * (size_t)num_components);
        if (new_rows[y] == NULL) {
            fprintf(stderr, "Blad alokacji pamieci.\n");
            exit(1);
        }
    }

    for (y = 0; y < old_height; y++) {
        for (x = 0; x < old_width; x++) {
            JDIMENSION new_x = old_height - 1 - y;
            JDIMENSION new_y = x;

            for (c = 0; c < num_components; c++) {
                new_rows[new_y][new_x * num_components + c] = row_pointers[y][x * num_components + c];
            }
        }
    }

    free_image();
    row_pointers = new_rows;
    width = new_width;
    height = new_height;
}

void process_file(void)
{
    if (strcmp(filter, "flip") == 0) {
        if (strcmp(axis, "x") == 0) {
            flip_x();
        } else if (strcmp(axis, "y") == 0) {
            flip_y();
        } else {
            fprintf(stderr, "Niepoprawna os odbicia. Uzyj: --axis x albo --axis y.\n");
            exit(1);
        }
    } else if (strcmp(filter, "rotate") == 0) {
        if (strcmp(direction, "left") == 0) {
            rotate_left();
        } else if (strcmp(direction, "right") == 0) {
            rotate_right();
        } else {
            fprintf(stderr, "Niepoprawny kierunek obrotu. Uzyj: --direction left albo --direction right.\n");
            exit(1);
        }
    } else {
        fprintf(stderr, "Niepoprawny filtr. Uzyj: --filter flip albo --filter rotate.\n");
        exit(1);
    }
}

void abort_(const char *s, ...)
{
    va_list args;
    va_start(args, s);
    vfprintf(stderr, s, args);
    fprintf(stderr, "\n");
    va_end(args);
    abort();
}

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 = (size_t)width * (size_t)num_components;
    row_pointers = (JSAMPARRAY)malloc(sizeof(JSAMPROW) * height);
    if (row_pointers == NULL) {
        abort_("Blad alokacji pamieci.");
    }

    for (y = 0; y < (int)height; y++) {
        row_pointers[y] = (JSAMPROW)malloc(rowbytes);
        if (row_pointers[y] == NULL) {
            abort_("Blad alokacji pamieci.");
        }
    }

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

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);
    free_image();
}

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 albo rotate");
    struct arg_str *axis_arg = arg_str0(NULL, "axis", "<x|y>", "Os odbicia dla filtru flip");
    struct arg_str *direction_arg = arg_str0(NULL, "direction", "<left|right>", "Kierunek obrotu dla filtru rotate");
    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,
        axis_arg,
        direction_arg,
        times_arg,
        help,
        end
    };

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

    times_arg->dval[0] = 1;
    axis = "x";
    direction = "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];

        if (axis_arg->count > 0) {
            axis = axis_arg->sval[0];
        }

        if (direction_arg->count > 0) {
            direction = direction_arg->sval[0];
        }
    } else {
        arg_print_errors(stderr, end, "geometry");
        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;
}
