// tail.c
// Řešení IJC-DU2, příklad a), 15.4.2024
// Autor: Roman Nečas, FIT
// Přeloženo: gcc 11.4.0

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdbool.h>

#define MAX_LINE_LENGTH 2048 // Maximalna dlzka riadku je 2047 + '\n'

typedef struct {
    char **buffer;  // pole ukazatelov na riadky
    int size;       // velkost bufferu
    int start;      // Index prveho riadku
    int count;      // pocet riadkov v bufferi
} CircularBuffer;

// vytvori kruhovy buffer s kapacitou n
CircularBuffer *cbuf_create(int n) {
    CircularBuffer *cb = (CircularBuffer *)malloc(sizeof(CircularBuffer));
    if (cb == NULL) {
        fprintf(stderr, "Error: Failed to allocate memory for circular buffer.\n");
        exit(EXIT_FAILURE);
    }
    cb->buffer = (char **)malloc(n * sizeof(char *));
    if (cb->buffer == NULL) {
        fprintf(stderr, "Error: Failed to allocate memory for line buffer.\n");
        exit(EXIT_FAILURE);
    }
    for (int i = 0; i < n; i++) {
        cb->buffer[i] = (char *)malloc((MAX_LINE_LENGTH+1) * sizeof(char));
        if (cb->buffer[i] == NULL) {
            fprintf(stderr, "Error: Failed to allocate memory for line.\n");
            exit(EXIT_FAILURE);
        }
    }
    cb->size = n;
    cb->start = 0;
    cb->count = 0;
    return cb;
}

// vloz riadok do bufferu
void cbuf_put(CircularBuffer *cb, char *line) {
    line[MAX_LINE_LENGTH] = '\0';
    line[MAX_LINE_LENGTH-1] = '\n';
    strncpy(cb->buffer[(cb->start + cb->count) % cb->size], line, MAX_LINE_LENGTH);
    cb->buffer[(cb->start + cb->count) % cb->size][MAX_LINE_LENGTH] = '\0';
    if (cb->count < cb->size) {
        cb->count++;
    } else {
        cb->start = (cb->start + 1) % cb->size;
    }
}

// nacitaj riadok z bufferu
char *cbuf_get(CircularBuffer *cb, int index) {
    if (index < 0 || index >= cb->count) {
        fprintf(stderr, "Error: Line index out of buffer range.\n");
        exit(EXIT_FAILURE);
    }
    return cb->buffer[(cb->start + index) % cb->size];
}

// vyscisti pamat
void cbuf_free(CircularBuffer *cb) {
    for (int i = 0; i < cb->size; i++) {
        free(cb->buffer[i]);
    }
    free(cb->buffer);
    free(cb);
}

bool isNumber(char *str) {
    for(int i = 0; str[i] != '\0'; i++) {
        if(!isdigit(str[i])) {
            return false;
        }
    }
    return true;
}
int main(int argc, char *argv[]) {
    int n = 10; // defaultny pocet riadkov
    FILE *file = stdin;

    // Parsing argumentov
    if (argc == 3 && strcmp(argv[1], "-n") == 0) {  // 3 argumenty nacitja z stdin
        if(isNumber(argv[2]) == false) 
            {
                fprintf(stderr, "Error: Invalid number of lines.\n");
                return 1; 
            } 
         n = atoi(argv[2]);
        if (n < 0) {
            fprintf(stderr, "Error: Invalid number of lines.\n");
            return EXIT_FAILURE;
        }
        else if (n == 0)
        {
            return 0;
        }
    }
    else if(argc == 4 && strcmp(argv[1], "-n") == 0) {   // 4 argumenty, subor je posledny
       if(isNumber(argv[2]) == false)
             {
                 fprintf(stderr, "Error: Invalid number of lines.\n");
                 return 1;
             }
         n = atoi(argv[2]);
        if (n < 0) {
              fprintf(stderr, "Error: Invalid number of lines.\n");
              return EXIT_FAILURE;
          }
          else if (n == 0)
          {
              return 0;
          }
            file = fopen(argv[3], "r");
            if (file == NULL) {
                 fprintf(stderr, "Error: Unable to open file '%s'.\n", argv[3]);
                return EXIT_FAILURE;
            }
    }
     else if(argc == 4 && strcmp(argv[2], "-n") == 0) {  // 4 argumenty, subor je 2
         if(isNumber(argv[3]) == false)
             {
                 fprintf(stderr, "Error: Invalid number of lines.\n");
                 return 1;
             }

          n = atoi(argv[3]);
         if (n < 0) {
               fprintf(stderr, "Error: Invalid number of lines.\n");
               return EXIT_FAILURE;
           }
           else if (n == 0)
           {
               return 0;
           }
             file = fopen(argv[1], "r");
             if (file == NULL) {
                  fprintf(stderr, "Error: Unable to open file '%s'.\n", argv[1]);
                 return EXIT_FAILURE;
             }
     }

     else if (argc == 2) {  // 2 argument, subor je druhy
        file = fopen(argv[1], "r");
        if (file == NULL) {
            fprintf(stderr, "Error: Unable to open file '%s'.\n", argv[1]);
            return EXIT_FAILURE;
        }
    } else if(argc != 1) {
        fprintf(stderr, "Usage: %s [-n number] [file]\n", argv[0]);
        return EXIT_FAILURE;
    }

    // Inicializacia kruhoveho bufferu
    CircularBuffer *cb = cbuf_create(n);

    // nacitame riadky
    char line[MAX_LINE_LENGTH];
    int islonger = 0; 
    while (fgets(line, sizeof(line), file) != NULL) {
        int len = strlen(line);
        if (len == (MAX_LINE_LENGTH-1) && line[MAX_LINE_LENGTH -1] != '\n') {
            islonger = 1;
            // zahodime zvysok riadku
            int c;
            while ((c = fgetc(file)) != '\n'&& c!= EOF);
        }
        cbuf_put(cb, line);
    }
    if (islonger == 1)
    {
        fprintf(stderr, "Too long line, above limit will be truncated\n"); 
    }

    //  vypiseme poslledne riadky
    int start_index = cb->count >= n ? cb->count - n : 0;
    for (int i = start_index; i < cb->count; i++) {
        printf("%s", cbuf_get(cb, i));
    }

    // vycisti pamet
    cbuf_free(cb);
    if (file != stdin) {
        fclose(file);
    }

    return EXIT_SUCCESS;
}

