#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;
double percent; // Nowa zmienna globalna dla parametru percent

/* 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;

/* Funkcja pomocnicza chroniąca przed przekroczeniem zakresu kolorów 0-255 */
unsigned char clamp(double value) {
    if (value < 0.0) return 0;
    if (value > 255.0) return 255;
    return (unsigned char)value;
}

void negate() {
    int x, y, c;
    if (color_space != JCS_RGB) return;

    for (y = 0; y < height; y++) {
        JSAMPROW row = row_pointers[y];
        for (x = 0; x < width; x++) {
            JSAMPROW ptr = &(row[x * 3]);
            // Negacja dla każdego z kanałów RGB: R, G, B
            for (c = 0; c < 3; c++) {
                ptr[c] = 255 - ptr[c];
            }
        }
    }
}

void brightness() {
    int x, y, c;
    if (color_space != JCS_RGB) return;

    // Współczynnik jasności: np. dla +20% będzie to 1.20, dla -10% będzie 0.90
    double factor = 1.0 + (percent / 100.0);

    for (y = 0; y < height; y++) {
        JSAMPROW row = row_pointers[y];
        for (x = 0; x < width; x++) {
            JSAMPROW ptr = &(row[x * 3]);
            for (c = 0; c < 3; c++) {
                ptr[c] = clamp(ptr[c] * factor);
            }
        }
    }
}

void contrast() {
    int x, y, c;
    if (color_space != JCS_RGB) return;

    for (y = 0; y < height; y++) {
        JSAMPROW row = row_pointers[y];
        for (x = 0; x < width; x++) {
            JSAMPROW ptr = &(row[x * 3]);
            for (c = 0; c < 3; c++) {
                ptr[c] = clamp(times * (ptr[c] - 127.0) + 127.0);
            }
        }
    }
}

void sepia() {
    int x, y;
    if (color_space != JCS_RGB) return;

    for (y = 0; y < height; y++) {
        JSAMPROW row = row_pointers[y];
        for (x = 0; x < width; x++) {
            JSAMPROW ptr = &(row[x * 3]);
            double r = ptr[0];
            double g = ptr[1];
            double b = ptr[2];

            ptr[0] = clamp(0.393 * r + 0.769 * g + 0.189 * b); // Nowy Red
            ptr[1] = clamp(0.349 * r + 0.686 * g + 0.168 * b); // Nowy Green
            ptr[2] = clamp(0.272 * r + 0.534 * g + 0.131 * b); // Nowy Blue
        }
    }
}

void flip(){
    int x, y;
    if (color_space != JCS_RGB)  return;
 
    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;
            }
        }
    } 
    else if (strcmp(axis, "x") == 0) {
        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() {
    if (color_space != JCS_RGB) return;
 
    JSAMPARRAY old_pointers = row_pointers;
    JDIMENSION old_width = width;
    JDIMENSION old_height = height;
 
    width = old_height;
    height = old_width;
 
    size_t rowbytes = width * num_components;
    row_pointers = (JSAMPARRAY) malloc(sizeof(j_common_ptr) * height);
    for (int y = 0; y < height; y++) {
        row_pointers[y] = (JSAMPROW) malloc(rowbytes);
    }
 
    for (int old_y = 0; old_y < old_height; old_y++) {
        for (int old_x = 0; old_x < old_width; old_x++) {
            int new_x, new_y;
 
            if (strcmp(direction, "right") == 0) {
                new_x = old_height - 1 - old_y;
                new_y = old_x;
            } else {
                new_x = old_y;
                new_y = old_width - 1 - old_x;
            }
 
            JSAMPROW old_ptr = &(old_pointers[old_y][old_x * 3]);
            JSAMPROW new_ptr = &(row_pointers[new_y][new_x * 3]);
            new_ptr[0] = old_ptr[0];
            new_ptr[1] = old_ptr[1];
            new_ptr[2] = old_ptr[2];
        }
    }
 
    for (int y = 0; y < old_height; y++) {
        free(old_pointers[y]);
    }
    free(old_pointers);
}

void process_file() {
    if (filter == NULL) return;

    if (strcmp(filter, "negate") == 0) {
        negate();
    }
    else if (strcmp(filter, "brightness") == 0) {
        brightness();
    }
    else if (strcmp(filter, "contrast") == 0) {
        contrast();
    }
    else if (strcmp(filter, "sepia") == 0) {
        sepia();
    } 
    else if(strcmp(filter, "flip") == 0){
        flip();
    } 
    else if(strcmp(filter, "rotate") == 0){
        rotate();
    }
    else {
        printf("Nieznany filtr: %s\n", filter);
    }
}

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

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

int main(int argc, char **argv){   
  // Opcje
  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>", "Rotate direction (right/left)");
  struct arg_dbl *times_arg = arg_dbl0("t", "times" , "<times>", "Multiplyer");
  struct arg_dbl *percent_arg = arg_dbl0("p", "percent", "<percent>", "Percentage (k%)"); // Dodano argument percent
  struct arg_lit *help = arg_lit0("h","help", "print this help and exit");
  struct arg_end *end = arg_end(10); 
  
  int nerrors;
  
  // Zaktualizowano tablicę argtable, umieszczając end na końcu
  void *argtable[] = {input_file_arg, output_file_arg, filter_arg, axis_arg, direction_arg, times_arg, percent_arg, help, end};
  
  if (arg_nullcheck(argtable) != 0) printf("error: insufficient memory\n");
  
  // Ustawienie wartości domyślnych dla parametrów opcjonalnych
  times_arg->dval[0] = 1.0;
  percent_arg->dval[0] = 0.0;
  axis_arg->sval[0] = "y";
  
  nerrors = arg_parse(argc, argv, argtable);

  if (help->count > 0){
     printf("Usage: point");
     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];
     percent = percent_arg->dval[0]; // Odczytanie wartości percent
  }
  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;
}