#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;
double times;
double percent;

JSAMPARRAY row_pointers = NULL;

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

// Функция для ограничения значений цвета в пределах 0-255
unsigned char limit_color(double value) {
    if (value > 255) return 255;
    if (value < 0) return 0;
    return (unsigned char)value;
}

void negate(){
    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]);
            ptr[0] = 255 - ptr[0]; 
            ptr[1] = 255 - ptr[1]; 
            ptr[2] = 255 - ptr[2]; 
        }
    }
}  

void brightness(double percent){
    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]);
            for (int i = 0; i < 3; i++) {
                ptr[i] = limit_color(ptr[i] + (ptr[i] * percent / 100.0));
            }
        }
    }
}

void contrast(double times){
    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]);
            for (int i = 0; i < 3; i++) {
                ptr[i] = limit_color(times * (ptr[i] - 127) + 127);
            }
        }
    }
}

// НОВАЯ ФУНКЦИЯ: Сепия
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]);
            int r = ptr[0];
            int g = ptr[1];
            int b = ptr[2];

            ptr[0] = limit_color(0.393 * r + 0.769 * g + 0.189 * b);
            ptr[1] = limit_color(0.349 * r + 0.686 * g + 0.168 * b);
            ptr[2] = limit_color(0.272 * r + 0.534 * g + 0.131 * b);
        }
    }
}

void process_file(){
    if(strcmp(filter, "negate") == 0){
        negate();
    }
    else if(strcmp(filter, "brightness") == 0){
        brightness(percent);
    }
    else if(strcmp(filter, "contrast") == 0){
        contrast(times);
    }
    else if(strcmp(filter, "sepia") == 0){
        sepia();
    }
}

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(JSAMPROW) * height);
    for (y=0; y<height; y++){
        row_pointers[y] = (JSAMPROW) malloc(rowbytes);
    }
    
    JSAMPARRAY tmp = row_pointers;
    while( cinfo.output_scanline < cinfo.image_height )
    {
        JDIMENSION lines_read = jpeg_read_scanlines( &cinfo, tmp, 1 );
        tmp += lines_read;
    }

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

    JSAMPARRAY tmp = row_pointers;
    while( cinfo.next_scanline < cinfo.image_height )
    {
        JDIMENSION lines_written = jpeg_write_scanlines( &cinfo, tmp, 1 );
        tmp += lines_written;
    }

    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){   
    struct arg_file *input_file_arg = arg_file1("i", "input-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 (negate, brightness, contrast, sepia)");
    struct arg_dbl *times_arg = arg_dbl0("t", "times" , "<times>", "Contrast multiplier");
    struct arg_dbl *percent_arg = arg_dbl0("p", "percent", "<percent>", "Brightness adjustment percentage");
    struct arg_lit *help = arg_lit0("h","help", "print this help and exit");
    struct arg_end *end = arg_end(10);
    
    void *argtable[] = {input_file_arg, output_file_arg, filter_arg, times_arg, percent_arg, help, end};
    
    if (arg_nullcheck(argtable) != 0) printf("error: insufficient memory\n");
    
    times_arg->dval[0] = 1.0;
    percent_arg->dval[0] = 0.0;
    
    int nerrors = arg_parse(argc, argv, argtable);
      
    if (help->count > 0 || nerrors > 0){
        if (nerrors > 0) arg_print_errors(stderr, end, "point");
        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 (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];
    percent = percent_arg->dval[0];
      
    read_jpeg_file(input_file);
    process_file();
    write_jpeg_file(output_file);

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