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


void flip(){
  int x, y, c;

  if (color_space != JCS_RGB) return;

  if (strcmp(axis, "x") == 0) {
    /*
      Odbicie względem osi X:
      góra <-> dół
    */
    for (y = 0; y < height / 2; y++) {
      JSAMPROW temp = row_pointers[y];
      row_pointers[y] = row_pointers[height - 1 - y];
      row_pointers[height - 1 - y] = temp;
    }
  }
  else if (strcmp(axis, "y") == 0) {
    /*
      Odbicie względem osi Y:
      lewa <-> prawa
    */
    for (y = 0; y < height; y++) {
      JSAMPROW row = row_pointers[y];

      for (x = 0; x < width / 2; x++) {
        JSAMPROW left = &(row[x * num_components]);
        JSAMPROW right = &(row[(width - 1 - x) * num_components]);

        for (c = 0; c < num_components; c++) {
          JSAMPLE temp = left[c];
          left[c] = right[c];
          right[c] = temp;
        }
      }
    }
  }
}


void rotate(){
  int x, y, c;
  JDIMENSION new_width;
  JDIMENSION new_height;
  size_t rowbytes;
  JSAMPARRAY new_rows;

  if (color_space != JCS_RGB) return;

  /*
    Po obrocie o 90 stopni szerokość i wysokość zamieniają się miejscami.
  */
  new_width = height;
  new_height = width;

  rowbytes = new_width * num_components;

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

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

  /*
    Obrót w prawo:
    stary piksel (x, y) trafia na pozycję:
    new_x = height - 1 - y
    new_y = x
  */
  if (strcmp(direction, "right") == 0) {
    for (y = 0; y < height; y++) {
      for (x = 0; x < width; x++) {
        int new_x = height - 1 - y;
        int new_y = x;

        JSAMPROW old_pixel = &(row_pointers[y][x * num_components]);
        JSAMPROW new_pixel = &(new_rows[new_y][new_x * num_components]);

        for (c = 0; c < num_components; c++) {
          new_pixel[c] = old_pixel[c];
        }
      }
    }
  }

  /*
    Obrót w lewo:
    stary piksel (x, y) trafia na pozycję:
    new_x = y
    new_y = width - 1 - x
  */
  else if (strcmp(direction, "left") == 0) {
    for (y = 0; y < height; y++) {
      for (x = 0; x < width; x++) {
        int new_x = y;
        int new_y = width - 1 - x;

        JSAMPROW old_pixel = &(row_pointers[y][x * num_components]);
        JSAMPROW new_pixel = &(new_rows[new_y][new_x * num_components]);

        for (c = 0; c < num_components; c++) {
          new_pixel[c] = old_pixel[c];
        }
      }
    }
  }
  else {
    printf("Unknown direction: %s\n", direction);
    printf("Use: left or right\n");

    for (y = 0; y < new_height; y++) {
      free(new_rows[y]);
    }
    free(new_rows);

    return;
  }

  /*
    Zwolnienie starego obrazu z pamięci.
  */
  for (y = 0; y < height; y++) {
    free(row_pointers[y]);
  }
  free(row_pointers);

  /*
    Podmiana starego obrazu na nowy, obrócony.
  */
  row_pointers = new_rows;
  width = new_width;
  height = new_height;
}


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_file Reads from a jpeg file on disk specified by filename and saves into the
 * raw_image buffer in an uncompressed format.
 *
 * \returns positive integer if successful, -1 otherwise
 * \param *filename char string specifying the file name to read from
 *
 */

void read_jpeg_file( const char *filename )
{
    /* these are standard libjpeg structures for reading(decompression) */
    struct jpeg_decompress_struct cinfo;
    struct jpeg_error_mgr jerr;

    /* libjpeg data structure for storing one row, that is, scanline of an image */
    int y;

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

    if ( !infile )
    {
        abort_("Error opening input jpeg file %s!\n", filename);
    }

    /* here we set up the standard libjpeg error handler */
    cinfo.err = jpeg_std_error( &jerr );
    jpeg_create_decompress( &cinfo );

    /* this makes the library read from infile */
    jpeg_stdio_src( &cinfo, infile );

    /* reading the image header which contains image information */
    jpeg_read_header( &cinfo, TRUE );

    /* Start decompression jpeg here */
    jpeg_start_decompress( &cinfo );

    width = cinfo.output_width;
    height = cinfo.output_height;
    num_components = cinfo.out_color_components;
    color_space = cinfo.out_color_space;

    /* allocate memory to hold the uncompressed image */
    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);
    }

    /* read one scan line at a time */
    y = 0;
    JSAMPARRAY tmp = row_pointers;

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

    /* wrap up decompression, destroy objects, free pointers and close open files */
    jpeg_finish_decompress( &cinfo );
    jpeg_destroy_decompress( &cinfo );

    fclose( infile );
}


/**
 * write_jpeg_file Writes the raw image data stored in the raw_image buffer
 * to a jpeg image with default compression and smoothing options in the file
 * specified by *filename.
 *
 * \returns positive integer if successful, -1 otherwise
 * \param *filename char string specifying the file name to save to
 *
 */

void write_jpeg_file( const char *filename )
{
    struct jpeg_compress_struct cinfo;
    struct jpeg_error_mgr jerr;
    int y;
    JSAMPARRAY tmp;

    /* this is a pointer to one row of image data */
    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);

    /* Setting the parameters of the output file here */
    cinfo.image_width = width;
    cinfo.image_height = height;
    cinfo.input_components = num_components;
    cinfo.in_color_space = color_space;

    /* default compression parameters, we shouldn't be worried about these */
    jpeg_set_defaults( &cinfo );
    jpeg_set_quality (&cinfo, quality, TRUE);

    /* Now do the compression */
    jpeg_start_compress( &cinfo, TRUE );

    /* like reading a file, this time write one row at a time */
    tmp = row_pointers;

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

    /* similar to read file, clean up after we're done compressing */
    jpeg_finish_compress( &cinfo );
    jpeg_destroy_compress( &cinfo );

    fclose( outfile );

    /* cleanup heap allocation */
    for (y = 0; y < height; y++){
        free(row_pointers[y]);
    }

    free(row_pointers);
}


int main(int argc, char **argv)
{
    /* Options */
    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>", "Axis for flip: x or y");
    struct arg_str *direction_arg = arg_str0("d", "direction", "<direction>", "Direction for rotate: left or right");

    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_arg->sval[0] = "x";
    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];
        axis = axis_arg->sval[0];
        direction = direction_arg->sval[0];
        times = times_arg->dval[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;
}