/*
   Sorter for GNU/Linux 64 bit version.
   Version: 2.0 - with hybrid QuickSort + MergeSort
   Translated from FreePascal to C.
   Copyright (C) 2025-2026 Artyomov Alexander
   Used https://chat.deepseek.com/
   http://self-made-free.ru/
   aralni@mail.ru

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU Affero General Public License as
   published by the Free Software Foundation, either version 3 of the
   License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Affero General Public License for more details.

   You should have received a copy of the GNU Affero General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <wchar.h>
#include <wctype.h>
#include <locale.h>
#include <time.h>
#include <errno.h>

/* ============================================================================
 * Constants
 * ============================================================================ */
#define INSERTION_SORT_THRESHOLD 16
#define MERGE_THRESHOLD 64
#define INITIAL_CAPACITY 8

/* ============================================================================
 * UCS4 String Type
 * ============================================================================ */
typedef struct {
    uint32_t *data;
    size_t length;
    size_t capacity;
} UCS4String;

/* Initialize empty UCS4 string */
void ucs4_init(UCS4String *s) {
    s->data = NULL;
    s->length = 0;
    s->capacity = 0;
}

/* Initialize with specified length */
void ucs4_init_len(UCS4String *s, size_t len) {
    s->length = len;
    s->capacity = len;
    if (len > 0) {
        s->data = (uint32_t*)malloc(len * sizeof(uint32_t));
        if (!s->data) {
            perror("malloc");
            exit(1);
        }
    } else {
        s->data = NULL;
    }
}

/* Clear and free memory */
void ucs4_clear(UCS4String *s) {
    if (s->data) {
        free(s->data);
        s->data = NULL;
    }
    s->length = 0;
    s->capacity = 0;
}

/* Grow capacity if needed */
static void ucs4_grow(UCS4String *s, size_t min_capacity) {
    size_t new_cap = s->capacity == 0 ? INITIAL_CAPACITY : s->capacity * 2;
    if (new_cap < min_capacity) new_cap = min_capacity;
    
    uint32_t *new_data = (uint32_t*)realloc(s->data, new_cap * sizeof(uint32_t));
    if (!new_data && new_cap > 0) {
        perror("realloc");
        exit(1);
    }
    s->data = new_data;
    s->capacity = new_cap;
}

/* Convert UTF-8 to UCS4 */
void ucs4_from_utf8(UCS4String *s, const char *utf8_str) {
    ucs4_clear(s);
    if (!utf8_str || *utf8_str == '\0') return;
    
    /* First pass: count characters */
    size_t count = 0;
    const char *p = utf8_str;
    mbstate_t state;
    memset(&state, 0, sizeof(state));
    
    while (*p) {
        wchar_t wc;
        int bytes = mbrtowc(&wc, p, 4, &state);
        if (bytes <= 0) {
            /* Invalid UTF-8, treat as single byte */
            bytes = 1;
        }
        p += bytes;
        count++;
    }
    
    /* Allocate memory */
    if (count > 0) {
        if (count > s->capacity) ucs4_grow(s, count);
        s->length = count;
        
        /* Second pass: fill data */
        p = utf8_str;
        memset(&state, 0, sizeof(state));
        size_t pos = 0;
        while (*p) {
            wchar_t wc;
            int bytes = mbrtowc(&wc, p, 4, &state);
            if (bytes <= 0) {
                wc = (wchar_t)(unsigned char)*p;
                bytes = 1;
            }
            s->data[pos++] = (uint32_t)wc;
            p += bytes;
        }
    }
}

/* Compare two UCS4 strings */
int ucs4_compare(const UCS4String *a, const UCS4String *b) {
    size_t min_len = a->length < b->length ? a->length : b->length;
    
    for (size_t i = 0; i < min_len; i++) {
        if (a->data[i] < b->data[i]) return -1;
        if (a->data[i] > b->data[i]) return 1;
    }
    
    if (a->length < b->length) return -1;
    if (a->length > b->length) return 1;
    return 0;
}

/* ============================================================================
 * Sort Item
 * ============================================================================ */
typedef struct {
    char *original_line;      /* Original UTF-8 string */
    UCS4String sort_key;      /* Key for sorting (whole line or field) */
} SortItem;

void sort_item_init(SortItem *item) {
    item->original_line = NULL;
    ucs4_init(&item->sort_key);
}

void sort_item_clear(SortItem *item) {
    if (item->original_line) {
        free(item->original_line);
        item->original_line = NULL;
    }
    ucs4_clear(&item->sort_key);
}

/* ============================================================================
 * Program Options
 * ============================================================================ */
typedef struct {
    char *input_file;
    int is_table;
    int column_index;          /* 0-based */
    UCS4String delimiter;
} SortOptions;

void options_init(SortOptions *opt) {
    opt->input_file = NULL;
    opt->is_table = 0;
    opt->column_index = 0;
    ucs4_init(&opt->delimiter);
}

void options_cleanup(SortOptions *opt) {
    if (opt->input_file) free(opt->input_file);
    ucs4_clear(&opt->delimiter);
}

/* ============================================================================
 * Field Extraction
 * ============================================================================ */
void extract_field(SortItem *item, int column_index, const UCS4String *delimiter) {
    ucs4_clear(&item->sort_key);
    
    UCS4String ucs_line;
    ucs4_init(&ucs_line);
    ucs4_from_utf8(&ucs_line, item->original_line);
    
    size_t field_count = 0;
    size_t start_pos = 0;
    uint32_t delim_char = (delimiter->length > 0) ? delimiter->data[0] : 9; /* tab by default */
    
    for (size_t i = 0; i <= ucs_line.length; i++) {
        int is_delimiter = (i == ucs_line.length) || 
                           (i < ucs_line.length && ucs_line.data[i] == delim_char);
        
        if (is_delimiter) {
            if (field_count == (size_t)column_index) {
                size_t field_len = i - start_pos;
                if (field_len > 0) {
                    ucs4_init_len(&item->sort_key, field_len);
                    memcpy(item->sort_key.data, 
                           ucs_line.data + start_pos, 
                           field_len * sizeof(uint32_t));
                } else {
                    ucs4_init_len(&item->sort_key, 0);
                }
                break;
            }
            field_count++;
            start_pos = i + 1;
        }
    }
    
    ucs4_clear(&ucs_line);
}

/* ============================================================================
 * File I/O
 * ============================================================================ */
SortItem* read_lines(const char *filename, SortOptions *opt, size_t *out_count) {
    FILE *fp = fopen(filename, "r");
    if (!fp) {
        fprintf(stderr, "Ошибка: не удалось открыть файл '%s': %s\n", filename, strerror(errno));
        return NULL;
    }
    
    /* First pass: count lines */
    size_t count = 0;
    char *line = NULL;
    size_t len = 0;
    ssize_t read;
    
    while ((read = getline(&line, &len, fp)) != -1) {
        count++;
    }
    
    printf("Файл содержит %zu строк\n", count);
    if (count < 1) {
        free(line);
        fclose(fp);
        return NULL;
    }
    
    /* Allocate array */
    SortItem *items = (SortItem*)calloc(count, sizeof(SortItem));
    if (!items) {
        perror("calloc");
        free(line);
        fclose(fp);
        return NULL;
    }
    
    /* Initialize items */
    for (size_t i = 0; i < count; i++) {
        sort_item_init(&items[i]);
    }
    
    /* Second pass: read data */
    rewind(fp);
    size_t idx = 0;
    
    while ((read = getline(&line, &len, fp)) != -1) {
        /* Remove trailing newline */
        if (read > 0 && (line[read-1] == '\n' || line[read-1] == '\r')) {
            line[read-1] = '\0';
            if (read > 1 && line[read-2] == '\r') line[read-2] = '\0';
        }
        
        items[idx].original_line = strdup(line);
        if (!items[idx].original_line) {
            perror("strdup");
            /* Cleanup already allocated items */
            for (size_t i = 0; i < idx; i++) {
                sort_item_clear(&items[i]);
            }
            free(items);
            free(line);
            fclose(fp);
            return NULL;
        }
        
        if (opt->is_table) {
            extract_field(&items[idx], opt->column_index, &opt->delimiter);
        } else {
            ucs4_from_utf8(&items[idx].sort_key, line);
        }
        idx++;
    }
    
    free(line);
    fclose(fp);
    *out_count = count;
    return items;
}

void write_lines(const char *filename, SortItem *items, size_t count) {
    FILE *fp = fopen(filename, "w");
    if (!fp) {
        fprintf(stderr, "Ошибка: не удалось открыть файл для записи '%s': %s\n", 
                filename, strerror(errno));
        return;
    }
    
    for (size_t i = 0; i < count; i++) {
        fprintf(fp, "%s", items[i].original_line);
        if (i < count - 1) fprintf(fp, "\n");
    }
    
    fclose(fp);
}

/* ============================================================================
 * Sorting Algorithms
 * ============================================================================ */
void insertion_sort(SortItem *items, int left, int right) {
    for (int i = left + 1; i <= right; i++) {
        SortItem temp = items[i];
        int j = i - 1;
        
        while (j >= left && ucs4_compare(&items[j].sort_key, &temp.sort_key) > 0) {
            items[j + 1] = items[j];
            j--;
        }
        items[j + 1] = temp;
    }
}

static void quick_sort_impl(SortItem *items, int left, int right) {
    if (right - left <= INSERTION_SORT_THRESHOLD) {
        insertion_sort(items, left, right);
        return;
    }
    
    /* Median of three */
    int mid = (left + right) / 2;
    
    if (ucs4_compare(&items[left].sort_key, &items[mid].sort_key) > 0) {
        SortItem temp = items[left];
        items[left] = items[mid];
        items[mid] = temp;
    }
    if (ucs4_compare(&items[left].sort_key, &items[right].sort_key) > 0) {
        SortItem temp = items[left];
        items[left] = items[right];
        items[right] = temp;
    }
    if (ucs4_compare(&items[mid].sort_key, &items[right].sort_key) > 0) {
        SortItem temp = items[mid];
        items[mid] = items[right];
        items[right] = temp;
    }
    
    UCS4String pivot = items[mid].sort_key;
    int i = left;
    int j = right;
    
    while (1) {
        while (i <= right && ucs4_compare(&items[i].sort_key, &pivot) < 0) i++;
        while (j >= left && ucs4_compare(&items[j].sort_key, &pivot) > 0) j--;
        
        if (i >= j) break;
        
        SortItem temp = items[i];
        items[i] = items[j];
        items[j] = temp;
        i++;
        j--;
    }
    
    if (left < j) quick_sort_impl(items, left, j);
    if (i < right) quick_sort_impl(items, i, right);
}

void merge(SortItem *items, int left, int mid, int right, SortItem *temp) {
    int i = left;
    int j = mid + 1;
    int k = 0;
    
    while (i <= mid && j <= right) {
        if (ucs4_compare(&items[i].sort_key, &items[j].sort_key) <= 0) {
            temp[k++] = items[i++];
        } else {
            temp[k++] = items[j++];
        }
    }
    
    while (i <= mid) temp[k++] = items[i++];
    while (j <= right) temp[k++] = items[j++];
    
    memcpy(&items[left], temp, k * sizeof(SortItem));
}

void merge_sort_impl(SortItem *items, int left, int right, SortItem *temp) {
    if (right - left <= MERGE_THRESHOLD) {
        quick_sort_impl(items, left, right);
        return;
    }
    
    int mid = left + (right - left) / 2;
    merge_sort_impl(items, left, mid, temp);
    merge_sort_impl(items, mid + 1, right, temp);
    merge(items, left, mid, right, temp);
}

void sort_lines(SortItem *items, size_t count) {
    if (count <= 1) return;  /* Nothing to sort */
    
    int last = (int)count - 1;
    
    if (count <= INSERTION_SORT_THRESHOLD) {
        insertion_sort(items, 0, last);
        return;
    }
    
    if (count <= MERGE_THRESHOLD) {
        quick_sort_impl(items, 0, last);
        return;
    }
    
    SortItem *temp = (SortItem*)malloc(count * sizeof(SortItem));
    if (!temp) {
        perror("malloc");
        quick_sort_impl(items, 0, last);
        return;
    }
    
    merge_sort_impl(items, 0, last, temp);
    free(temp);
}

/* ============================================================================
 * Help & Utilities
 * ============================================================================ */
void print_help(void) {
    printf("Использование:\n");
    printf("  sortucs4 - выводит эту справку\n");
    printf("  sortucs4 <файл> - сортирует строки файла UTF-8\n");
    printf("  sortucs4 <файл> <столбец> - сортирует таблицу (разделитель - табуляция) по указанному столбцу\n");
    printf("  sortucs4 <файл> <столбец> <разделитель> - сортирует таблицу с указанным разделителем\n");
}

double get_time_seconds(void) {
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    return ts.tv_sec + ts.tv_nsec / 1000000000.0;
}

/* ============================================================================
 * Main
 * ============================================================================ */
int main(int argc, char *argv[]) {
    /* Set locale for UTF-8 support */
    setlocale(LC_ALL, "en_US.UTF-8");
    
    if (argc == 1 || (argc >= 2 && (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0))) {
        print_help();
        return 0;
    }
    
    SortOptions options;
    options_init(&options);
    
    /* Save filename before any modifications */
    char *filename = strdup(argv[1]);
    if (!filename) {
        perror("strdup");
        return 1;
    }
    options.input_file = filename;
    
    if (argc >= 3) {
        options.is_table = 1;
        options.column_index = atoi(argv[2]) - 1;  /* Convert to 0-based */
        if (options.column_index < 0) options.column_index = 0;
    }
    
    if (argc >= 4) {
        ucs4_from_utf8(&options.delimiter, argv[3]);
    } else if (options.is_table) {
        /* Default delimiter is tab */
        ucs4_init_len(&options.delimiter, 1);
        if (options.delimiter.data) {
            options.delimiter.data[0] = 9;
        }
    }
    
    double start_time = get_time_seconds();
    
    size_t line_count = 0;
    SortItem *items = read_lines(options.input_file, &options, &line_count);
    
    if (!items || line_count == 0) {
        options_cleanup(&options);
        fprintf(stderr, "Ошибка: нет данных для сортировки\n");
        return 1;
    }
    
    double load_time = get_time_seconds() - start_time;
    printf("Файл загружен в память за %.3f секунд\n", load_time);
    
    if (line_count > 1) {
        printf("Начинается сортировка...\n");
        start_time = get_time_seconds();
        sort_lines(items, line_count);
        double sort_time = get_time_seconds() - start_time;
        printf("Сортировка завершена за %.3f секунд\n", sort_time);
    }
    
    start_time = get_time_seconds();
    write_lines(options.input_file, items, line_count);
    double write_time = get_time_seconds() - start_time;
    printf("Файл записан за %.3f секунд\n", write_time);
    
    /* Cleanup */
    for (size_t i = 0; i < line_count; i++) {
        sort_item_clear(&items[i]);
    }
    free(items);
    
    /* Use saved filename for output */
    printf("Файл \"%s\" успешно отсортирован.\n", options.input_file);
    
    options_cleanup(&options);
    
    return 0;
}