#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; //d
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;
 
// Odbicia (Flip) 
void flip(){
    int x, y;
    if (color_space != JCS_RGB)  return;
 
    if (strcmp(axis, "y") == 0) {
        // Odbicie poziome (względem osi Y)
        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(axis, "x") == 0) {
        // Odbicie pionowe (względem osi X)
        
        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;
        }
    }
}    
 
//  Obrót (Rotate) 
void rotate() {
    if (color_space != JCS_RGB) return;
 
    // Po obrocie o 90 stopni szerokosc staje się wysokoscia i na odwrot
    JDIMENSION new_width = height;
    JDIMENSION new_height = width;
    size_t rowbytes = new_width * num_components;
 
    //nowa pamiec na obrocony obraz
    JSAMPARRAY new_row_pointers = (JSAMPARRAY) malloc(sizeof(j_common_ptr) * new_height);
    for (int y = 0; y < new_height; y++) {
        new_row_pointers[y] = (JSAMPROW) malloc(rowbytes);
    }
 
    if (strcmp(direction, "right") == 0) {
        // Obrót w prawo 
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                int nx = height - 1 - y;
                int ny = x;
                new_row_pointers[ny][nx*3 + 0] = row_pointers[y][x*3 + 0];
                new_row_pointers[ny][nx*3 + 1] = row_pointers[y][x*3 + 1];
                new_row_pointers[ny][nx*3 + 2] = row_pointers[y][x*3 + 2];
            }
        }
    } else if (strcmp(direction, "left") == 0) {
        // Obrót w lewo 
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                int nx = y;
                int ny = width - 1 - x;
                new_row_pointers[ny][nx*3 + 0] = row_pointers[y][x*3 + 0];
                new_row_pointers[ny][nx*3 + 1] = row_pointers[y][x*3 + 1];
                new_row_pointers[ny][nx*3 + 2] = row_pointers[y][x*3 + 2];
            }
        }
    }
 
    // Zwalnianie pamięci
    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 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 );
//     free( row_pointer[0] );
    fclose( infile );
    /* yup, we succeeded! */
}
 
/**
* 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>", "Flip axis (x/y)");
  
  
  struct arg_str *direction_arg = arg_str0("d", "direction" , "<direction>", "Rotation direction (left/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); // maksymalna liczba błędów 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");
  
  times_arg->dval[0] = 1;
  axis_arg->sval[0] = "y";
  direction_arg->sval[0] = "right"; //wartosc domyslna kierunku
  
  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]; //odczyt kierunku
     
     times = times_arg->dval[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;  
}