#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdbool.h>
// Структура для зберігання інформації про регіон
typedef struct {
    char name[40];
    float square;
    int population; 
} Record;
FILE *currentFile;

// Функція для перевірки валідності назви файлу
bool isValidFileName(const char *fileName) 
{
    // перевірка, чи рядок fileName не є порожнім 
    if (fileName[0] == '\0') 
    {
        return false;
    }
    int i; 
    for (i = 0; fileName[i] != '\0'; i++) 
    {
        if (!isalnum((unsigned char)fileName[i]) && fileName[i] != '.') 
        {
            return false; 
        }
    }
    return true;
}

// Функція для перевірки валідності назви регіону
bool isValidRegionName(const char *name) {
    while (*name) {
        if (!isalpha((unsigned char)*name) && !isspace((unsigned char)*name)) {
            return false;
        }
        name++;
    }
    return true;
}
// Функція для перевірки валідності площі регіону
bool isValidArea(float area) {
    return area > 0;
}

// Функція для перевірки валідності населення регіону
bool isValidPopulation(int population) {
    return population >= 0;
}
// Функція для порівняння двох записів за назвою регіону
int compareRecordsByName(const void *a, const void *b) {
    const Record *recordA = (const Record *)a;
    const Record *recordB = (const Record *)b;
    return strcmp(recordA->name, recordB->name);
}
// Функція для порівняння двох записів за площею регіону
int compareRecordsByArea(const void *a, const void *b) {
    const Record *recordA = (const Record *)a;
    const Record *recordB = (const Record *)b;
    if (recordA->square < recordB->square) return -1;
    if (recordA->square > recordB->square) return 1;
    return 0;
}
// Функція для порівняння двох записів за населенням регіону
int compareRecordsByPopulation(const void *a, const void *b) {
    const Record *recordA = (const Record *)a;
    const Record *recordB = (const Record *)b;
    if (recordA->population < recordB->population) return -1;
    if (recordA->population > recordB->population) return 1;
    return 0;
}
// Функція для виводу інформації про записи
void displayRecords(const Record *records, int count) {
    int i;
    for (i = 0; i < count; i++) {
        printf("Region Name: %s, Area: %.2f, Population: %d\n",
               records[i].name, records[i].square, records[i].population);
    }
}
// Функція для очищення буфера вводу
void clearInputBuffer() {
    int c;
    while ((c = getchar()) != '\n' && c != EOF) {}
}
// Функція для виводу вмісту файлу
void displayFileContents(const char* fileName) {
    FILE *readFile = fopen(fileName, "rb");
    if (readFile == NULL) {
        printf("---Error opening file for content display---\n");
        return;
    }
    Record record;
    printf("\n---File Content---\n");
    while (fread(&record, sizeof(Record), 1, readFile) == 1) {
        printf("Region Name: %s, Area: %.2f, Population: %d\n", record.name, record.square, record.population);
    }
    fclose(readFile);
}

// Функція для створення файлу
void createFile() 
{
    char fileName[50]; // oголошення рядка, в який буде зчитуватися назва файлу
    
    printf("Enter the file name: ");
    fgets(fileName, sizeof(fileName), stdin); // зчитування назви файлу
    
    // видаляє символ нового рядка ('\n') з кінця рядка fileName
    // strcspn()-знаходить позицію першого символу нового рядка або повертає довжину рядка, якщо символ нового рядка не знайдений
    fileName[strcspn(fileName, "\n")] = '\0';
    
    // перевірка, чи назва файлу є валідною
    if (!isValidFileName(fileName)) 
    {
        printf("---Invalid file name. Only alphanumeric characters and dot are allowed---\n");
        return;
    }
    
    // відкриття файлу для запису в бінарному режимі. 
    FILE *currentFile = fopen(fileName, "wb");
    
    // перевірка, чи вдалося відкрити файл.
    if (currentFile == NULL) 
    {
        // якщо відкриття файлу не вдалося, виводиться повідомлення про помилку за допомогою perror
        perror("---Error creating file---\n");
    } 
    else 
    {
        printf("---File created successfully---\n");
        printf("File Name: %s\n", fileName); // виведення назви створеного файлу
        fclose(currentFile); // закриття файлу після створення
    }
}
// Функція для відкриття файлу
void openFile() {
    char fileName[50];
    printf("Enter the file name to open: ");
    fgets(fileName, sizeof(fileName), stdin);
    fileName[strcspn(fileName, "\n")] = 0;
    if (!isValidFileName(fileName)) {
        printf("---Invalid file name. Only alphanumeric characters and dot are allowed---\n");
        return;
    }
    currentFile = fopen(fileName, "rb");
    if (currentFile == NULL) {
        perror("---Error opening file---\n");
        return;
    } else {
        fseek(currentFile, 0, SEEK_END);
        long fileSize = ftell(currentFile);
        if (fileSize == 0) {
            printf("---File is empty---\n");
            printf("---File opened successfully---\n");
        } else {
            rewind(currentFile);
            printf("---File opened successfully---\n");
            Record record;
            while (fread(&record, sizeof(Record), 1, currentFile) == 1) {
                printf("Region Name: %s, Area: %.2f, Population: %d\n", record.name, record.square, record.population);
            }
        }
        char option;
        do {
            printf("\nDo you want to close the file? (y/n): ");
            option = getchar();
            while (getchar() != '\n');
            if (option == 'y' || option == 'Y') {
                fclose(currentFile);
                currentFile = NULL; 
                printf("---File closed successfully---\n");
            } else if (option == 'n' || option == 'N') {
                printf("---File remains open. Please press 'y' if you want to close it or 'n' to keep it open---\n");
            } else {
                printf("---Invalid option. Please press 'y' to close or 'n' to keep it open---\n");
            }
        } while(option != 'y' && option != 'Y');
        fclose(currentFile);
    }
}
// Функція для запису даних у файл
void writeToFile() {
    Record record;
    char fileName[50];
    char line[100];
    printf("Enter the file name to write to: ");
    fgets(fileName, sizeof(fileName), stdin);
    fileName[strcspn(fileName, "\n")] = '\0';
    FILE *file = fopen(fileName, "rb");
    if (file != NULL) {
        fclose(file);
    } else {
        printf("---File %s does not exist, writing is not possible---\n", fileName);
        return;
    }
    file = fopen(fileName, "ab");
    if (file == NULL) {
        printf("---Error opening file %s---\n", fileName);
        return;
    }
    printf("Enter the region name: ");
    fgets(line, sizeof(line), stdin);
    sscanf(line, "%39s", record.name);
    if (!isValidRegionName(record.name)) {
        printf("---Invalid region name. Only Latin letters are allowed---\n");
        fclose(file);
        return;
    }
    printf("Enter the area: ");
    fgets(line, sizeof(line), stdin);
    float area;
    if (sscanf(line, "%f", &area) == 1) {
       if (isValidArea(area)) {
          record.square = area;  
    printf("Enter the population: ");
    fgets(line, sizeof(line), stdin);
    int population;
    if (sscanf(line, "%d", &population) == 1) {
        if (isValidPopulation(population)) {
           record.population = population; 
           fwrite(&record, sizeof(Record), 1, file);
           printf("---Data successfully written to file %s---\n", fileName);
        } else {
           printf("---Invalid population input. Only positive integers are allowed---\n");
        }
           } else {
               printf("---Invalid input. Expected a number for population---\n");
                 }
               } else {
           printf("---Invalid area input. Only positive numbers are allowed---\n");
       }
    } else {
      printf("---Invalid input. Expected a number for area---\n");
    }
    fclose(file);
}
// Функція для видалення запису з файлу
void deleteRecord() {
    char fileName[50];
    char searchName[40];
    char line[100];
    Record record;
    printf("Enter the file name to delete from: ");
    if (fgets(fileName, sizeof(fileName), stdin) != NULL) {
        fileName[strcspn(fileName, "\n")] = 0;
        currentFile = fopen(fileName, "r+b");
        if (currentFile == NULL) {
            printf("---Error opening file %s---\n", fileName);
            return;
        }
        fseek(currentFile, 0, SEEK_END);
        long fileSize = ftell(currentFile);
        if (fileSize == 0) {
           printf("---File %s is empty, no records to delete---\n", fileName);
           fclose(currentFile);
           return;
        }
        rewind(currentFile);              
        printf("Enter the region name to delete: ");
        if (fgets(searchName, sizeof(searchName), stdin) != NULL) {
            searchName[strcspn(searchName, "\n")] = 0; 
            FILE *tempFile = fopen("temp.dat", "wb");
            if (tempFile == NULL) {
                printf("---Error opening temporary file---\n");
                fclose(currentFile);
                return;
            }
            fseek(currentFile, 0, SEEK_SET);
            while (fread(&record, sizeof(Record), 1, currentFile) == 1) {
                if (strcmp(record.name, searchName) != 0) {
                    fwrite(&record, sizeof(Record), 1, tempFile);
                }
            }
            fclose(currentFile);
            fclose(tempFile);
            remove(fileName); 
            rename("temp.dat", fileName); 
            printf("---Record successfully deleted from %s---\n", fileName);
        } else {
            printf("---Error reading region name---\n");
        }
    } else {
        printf("---Error reading file name---\n");
    }
}
// Функція для видалення файлу
void deleteFile() {
    char fileName[50];
    printf("Enter the file name to delete: ");
    if (fgets(fileName, sizeof(fileName), stdin) != NULL) {
        fileName[strcspn(fileName, "\n")] = 0;
        if (remove(fileName) == 0) {
            printf("---File successfully deleted---\n");
            currentFile = NULL;
        } else {
            printf("---Error deleting file---\n");
        }
    } else {
        printf("---Error reading file name---\n");
    }
}
// Функція для редагування запису у файлі
void editRecord() {
    char fileName[50];
    char searchName[40];
    char line[100];
    Record record;
    int found = 0;

    printf("Enter the file name to edit records: ");
    if (fgets(fileName, sizeof(fileName), stdin) != NULL) {
        fileName[strcspn(fileName, "\n")] = 0;
        FILE *editFile = fopen(fileName, "r+b");
        if (editFile == NULL) {
            printf("---Error opening file---\n");
            return;
        }
        fseek(editFile, 0, SEEK_END);
        long fileSize = ftell(editFile);
        if (fileSize == 0) {
           printf("---File %s is empty, no records to edit---\n", fileName);
           fclose(editFile);
           return;
        }
        rewind(editFile);
        printf("Enter the region name to edit: ");
        if (fgets(searchName, sizeof(searchName), stdin) != NULL) {
            searchName[strcspn(searchName, "\n")] = 0;
            fseek(editFile, 0, SEEK_SET);
            while (fread(&record, sizeof(Record), 1, editFile) == 1) {
                if (strcasecmp(record.name, searchName) == 0) { 
                    found = 1;
                    printf("Enter the new region name: ");
                    fgets(record.name, sizeof(record.name), stdin);
                    record.name[strcspn(record.name, "\n")] = 0;
                    printf("Enter the new area: ");
                    if (fgets(line, sizeof(line), stdin) && sscanf(line, "%f", &record.square) == 1) {
                        printf("Enter the new population: ");
                        if (fgets(line, sizeof(line), stdin) && sscanf(line, "%d", &record.population) == 1) {
                            fseek(editFile, -sizeof(Record), SEEK_CUR); 
                            size_t result = fwrite(&record, sizeof(Record), 1, editFile);
                            if (result == 1) {
                                printf("---Record successfully edited---\n");
                            } else {
                                printf("---Error writing data to file---\n");
                            }
                            break; 
                        } else {
                            printf("---Invalid input. Expected a number for population---\n");
                        }
                    } else {
                        printf("---Invalid input. Expected a number for area---\n");
                    }
                }
            }
            if (!found) {
                printf("---Record with the name %s not found---\n", searchName);
            }
            fclose(editFile);
        } else {
            printf("---Error reading region name---\n");
        }
    } else {
        printf("---Error reading file name---\n");
    }
}
// Функція для сортування записів у файлі
void sortRecords() {
    char fileName[50];
    char line[100];
    int choice;
    printf("Enter the file name to sort: ");
    if (fgets(fileName, sizeof(fileName), stdin) != NULL) {
        fileName[strcspn(fileName, "\n")] = 0;
        FILE *file = fopen(fileName, "r+b");
        if (file == NULL) {
            printf("---Error opening file %s. File may not exist---\n", fileName);
            return;
        }
    fseek(file, 0, SEEK_END);
    long fileSize = ftell(file);
    if (fileSize == 0) {
        printf("---File %s is empty, no records to sort---\n", fileName);
        fclose(file);
        return;
    }
    rewind(file);
    int recordCount = fileSize / sizeof(Record);
    Record *records = (Record *)malloc(recordCount * sizeof(Record));
    if (records == NULL) {
        printf("---Error allocating memory---\n");
        fclose(file);
        return;
    }
    fread(records, sizeof(Record), recordCount, file);
    rewind(file);
    printf("---Data before sorting---\n");
    displayRecords(records, recordCount);
    printf("Choose a field for sorting:\n");
    printf("1. Region Name\n");
    printf("2. Area\n");
    printf("3. Population\n");
    char line[100];
    int choice;
    if (fgets(line, sizeof(line), stdin) && sscanf(line, "%d", &choice) == 1) {
        switch (choice) {
            case 1:
                qsort(records, recordCount, sizeof(Record), compareRecordsByName);
                break;
            case 2:
                qsort(records, recordCount, sizeof(Record), compareRecordsByArea);
                break;
            case 3:
                qsort(records, recordCount, sizeof(Record), compareRecordsByPopulation);
                break;
            default:
                printf("---Invalid sorting choice---\n");
                free(records);
                fclose(file);
                return;
        }
        printf("---Data after sorting---\n");
        displayRecords(records, recordCount);
        fwrite(records, sizeof(Record), recordCount, file);
        free(records);
        fclose(file);
        printf("---Records successfully sorted in file %s---\n", fileName);
    } else {
        printf("---Invalid input, please enter a number---\n");
    }
} else {
    printf("---Error reading file name---\n");
   }
}
// Функція для вставки запису у відсортований файл
void insertIntoSortedFile() {
    char fileName[50];
    char line[100];
    char extra;
    Record newRecord;
    printf("Enter the sorted file name to insert into: ");
    if (fgets(fileName, sizeof(fileName), stdin) != NULL) {
        fileName[strcspn(fileName, "\n")] = 0;
        FILE *file = fopen(fileName, "r+b");
        if (file == NULL) {
            printf("---Error opening file %s---\n", fileName);
            return;
        }
        fseek(file, 0, SEEK_END);
        long fileSize = ftell(file);
        if (fileSize == 0) {
            printf("---File %s is empty, no records to insert---\n", fileName);
            fclose(file);
            return;
        }
        rewind(file);
        int recordCount = fileSize / sizeof(Record);
        Record *records = (Record *)malloc((recordCount + 1) * sizeof(Record));
        if (records == NULL) {
            printf("---Error allocating memory---\n");
            fclose(file);
            return;
        }
        fread(records, sizeof(Record), recordCount, file);
        int sortedBy = 3;
        int i;
        for (i = 0; i < recordCount - 1; i++) {
            if (records[i].population > records[i + 1].population) {
                sortedBy = 2; 
                break;
            }
        }
        printf("Enter the region name: ");
        if (fgets(newRecord.name, sizeof(newRecord.name), stdin) != NULL) {
            newRecord.name[strcspn(newRecord.name, "\n")] = 0;
            printf("Enter the area: ");
            if (fgets(line, sizeof(line), stdin) && sscanf(line, "%f", &newRecord.square) == 1 && isValidArea(newRecord.square)) {
                printf("Enter the population: ");
                if (fgets(line, sizeof(line), stdin) && sscanf(line, "%d%c", &newRecord.population, &extra) == 2 && isValidPopulation(newRecord.population)) {
                    int insertIndex;
                    for (insertIndex = 0; insertIndex < recordCount; insertIndex++) {
                        if (sortedBy == 3 && records[insertIndex].population > newRecord.population) break; 
                        if (sortedBy == 2 && records[insertIndex].square > newRecord.square) break;
                    }
                    memmove(&records[insertIndex + 1], &records[insertIndex], (recordCount - insertIndex) * sizeof(Record));
                    memcpy(&records[insertIndex], &newRecord, sizeof(Record));
                    fseek(file, 0, SEEK_SET);
                    fwrite(records, sizeof(Record), recordCount + 1, file);
                    printf("---Data after insertion---\n");
                    displayRecords(records, recordCount + 1); 
                    free(records);
                    fclose(file);
                    printf("---Record successfully inserted into %s and data displayed---\n", fileName);
                } else {
                    printf("---Invalid input for population. Only positive integers are allowed---\n");
                }
            } else {
                printf("---Invalid input for area. Only positive numbers are allowed---\n");
            }
        } else {
            printf("---Error reading region name---\n");
        }
    } else {
        printf("---Error reading file name---\n");
    }
}
// Функція для читання та виведення записів з файлу
void readAndDisplayRecords() {
    char fileName[50];
    char line[100];
    printf("Enter the file name to read and display records: ");
    if (fgets(line, sizeof(line), stdin) != NULL) {
        sscanf(line, "%49s", fileName);
        FILE *readFile = fopen(fileName, "rb");
        if (readFile == NULL) {
            printf("---Error opening file---\n");
            return;
        }
        fseek(readFile, 0, SEEK_END);
        long fileSize = ftell(readFile);
        if (fileSize == 0) {
            printf("---The file is empty---\n");
        } else {
            rewind(readFile);
            Record record;
            while (fread(&record, sizeof(Record), 1, readFile) == 1) {
                printf("Region Name: %s, Area: %.2f, Population: %d\n", record.name, record.square, record.population);
            }
        }
        fclose(readFile);
    } else {
        printf("---Error reading file name---\n");
    }
}

int main() 
{
    int choice; // зміннa для зберігання введеного номеру меню
    char line[100]; // масив, який використовується для зберігання введеного рядка 
    
    while (1) 
    {
        // вивід головного меню
        printf("\n--- Main Menu ---\n");
        printf("1. File Operations\n");
        printf("2. Record Operations\n");
        printf("3. Exit\n");
        printf("Enter your choice: ");
        
        // зчитування вибору користувача та обробка
        // sscanf зчитує значення з рядка line згідно з форматом %d, який вказує, що ми очікуємо ціле число.
        if (fgets(line, sizeof(line), stdin) && sscanf(line, "%d", &choice) == 1) 
        {
            // кейси для різних опцій меню
            switch (choice) 
            {
                // обробка опції File Operations
                case 1:
                    printf("\n--- File Menu ---\n");
                    printf("1. Create File\n");
                    printf("2. Open File\n");
                    printf("3. Delete File\n");
                    printf("0. Return to Main Menu\n");
                    printf("Enter your choice: ");
                    if (fgets(line, sizeof(line), stdin) && sscanf(line, "%d", &choice) == 1) 
                    {
                        switch (choice) 
                        {
                            case 1:
                                createFile();
                                break;
                            case 2:
                                openFile();
                                break;
                            case 3:
                                deleteFile();
                                break;
                            case 0:
                                break;
                            default:
                                printf("---Invalid choice, please try again---\n");
                        }
                    } 
                    else 
                    {
                        printf("---Invalid input, please enter a number---\n");
                    }
                    break;
                // обробка опції Record Operations
                case 2: 
                    printf("\n--- Records Menu ---\n");
                    printf("1. Write Data to File\n");
                    printf("2. Read and Display Records\n");
                    printf("3. Edit Record in File\n");
                    printf("4. Sort Records in File\n");
                    printf("5. Insert into Sorted File\n");
                    printf("6. Delete Record from File\n");
                    printf("0. Return to Main Menu\n");
                    printf("Enter your choice: ");
                    if (fgets(line, sizeof(line), stdin) && sscanf(line, "%d", &choice) == 1) 
                    {
                        switch (choice) 
                        {
                            case 1:
                                writeToFile();
                                break;
                            case 2:
                                readAndDisplayRecords();
                                break;
                            case 3:
                                editRecord();
                                break;
                            case 4:
                                sortRecords();
                                break;
                            case 5:
                                insertIntoSortedFile();
                                break;
                            case 6:
                                deleteRecord();
                                break;
                            case 0:
                                break;
                            default:
                                printf("---Invalid choice, please try again---\n");
                        }
                    } 
                    else 
                    {
                        printf("---Invalid input, please enter a number---\n");
                    }
                    break;
                case 3:
                    printf("Exiting the program.\n");
                    return 0;
                default:
                    printf("---Invalid choice, please try again---\n");
            }
        } 
        else 
        {
            printf("---Invalid input, please enter a number---\n");
            clearInputBuffer();
        }
    }
    getch();
    return 0;
}
