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

/* Указатель для хранения сырых, несжатых пикселей изображения */
JSAMPARRAY row_pointers = NULL;

/* Размеры и свойства изображения */
JDIMENSION width;
JDIMENSION height;
int num_components;
int quality = 75;
J_COLOR_SPACE color_space;

void flip(){
  int x, y;
  if (color_space != JCS_RGB || axis == NULL) return;
  
  if (strcmp(axis, "x") == 0) {
    // Вертикальное отражение (верх-низ)
    for (y = 0; y < height / 2; y++) {
      JSAMPROW row1 = row_pointers[y];
      JSAMPROW row2 = row_pointers[height - y - 1];
      JSAMPLE tmp;
      
      for (x = 0; x < width * num_components; x++) {
        tmp = row1[x];
        row1[x] = row2[x];
        row2[x] = tmp;
      }
    }
  } else if (strcmp(axis, "y") == 0) {
    // Горизонтальное отражение (лево-право)
    for (y = 0; y < height; y++) {
      JSAMPROW row = row_pointers[y];
      for (x = 0; x < width / 2; x++) {
        int x1 = x * num_components;
        int x2 = (width - x - 1) * num_components;
        
        for (int c = 0; c < num_components; c++) {
          JSAMPLE tmp = row[x1 + c];
          row[x1 + c] = row[x2 + c];
          row[x2 + c] = tmp;
        }
      }
    }
  }
}

void rotate(){
  int x, y, c;
  JSAMPARRAY new_row_pointers;

  if (direction == NULL) return;

  // Выделяем память под новые размеры (ширина и высота меняются местами)
  new_row_pointers = (JSAMPARRAY) malloc(sizeof(JSAMPROW) * width);
  for (y = 0; y < width; y++) {
    new_row_pointers[y] = (JSAMPROW) malloc(height * num_components);
  }

  if (strcmp(direction, "left") == 0) {
    // Поворот на 90 градусов влево (против часовой стрелки)
    for (y = 0; y < height; y++) {
      for (x = 0; x < width; x++) {
        int old_pixel_idx = x * num_components;
        int new_pixel_idx = y * num_components;
        int new_y = width - 1 - x;
        
        for (c = 0; c < num_components; c++) {
          new_row_pointers[new_y][new_pixel_idx + c] = row_pointers[y][old_pixel_idx + c];
        }
      }
    }
  } else if (strcmp(direction, "right") == 0) {
    // Поворот на 90 градусов вправо (по часовой стрелке)
    for (y = 0; y < height; y++) {
      for (x = 0; x < width; x++) {
        int old_pixel_idx = x * num_components;
        int new_pixel_idx = (height - 1 - y) * num_components;
        int new_y = x;
        
        for (c = 0; c < num_components; c++) {
          new_row_pointers[new_y][new_pixel_idx + c] = row_pointers[y][old_pixel_idx + c];
        }
      }
    }
  } else {
    // Если передан неверный параметр направления, чистим память и выходим
    for (y = 0; y < width; y++) {
      free(new_row_pointers[y]);
    }
    free(new_row_pointers);
    return;
  }

  // Освобождаем старую память картинки
  for (y = 0; y < height; y++) {
    free(row_pointers[y]);
  }
  free(row_pointers);
  
  // Переключаемся на новые данные и обновляем размеры
  row_pointers = new_row_pointers;
  JDIMENSION temp = width;
  width = height;
  height = temp;
}

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

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

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);
  }
  
  while (cinfo.output_scanline < cinfo.output_height) {
    y = cinfo.output_scanline;
    jpeg_read_scanlines(&cinfo, &row_pointers[y], 1);
  }
  
  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);
  
  for (y = 0; y < height; y++) {
    jpeg_write_scanlines(&cinfo, &row_pointers[y], 1);
  }
  
  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", "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 or rotate)");
  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_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, help, end};
  
  if (arg_nullcheck(argtable) != 0) {
    printf("error: insufficient memory\n");
    return 1;
  }
  
  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];
    
    if (axis_arg->count > 0) {
      axis = axis_arg->sval[0];
    } else {
      axis = NULL;
    }
    
    if (direction_arg->count > 0) {
      direction = direction_arg->sval[0];
    } else {
      direction = NULL;
    }
  } 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;
}