/*==============================================================================
  Лабораторна робота №8. Варіант 19.
  Тема: покажчики та посилання. Покажчики на функції.

  Умова (таблиця 8.1, варіант 19): розробити програму, команди меню якої
  передбачають такі дії:
    - створити одновимірний масив додатних та від'ємних чисел, задавши їх
      кількість з клавіатури;
    - вивести створений масив на екран;
    - виконати такі операції:
        замінити від'ємні числа їх квадратами;
        упорядкувати масив за спаданням;
        визначити суму парних чисел;
    - вивести на екран усі перетворення вхідного масиву.

  ОБОВ'ЯЗКОВІ ВИМОГИ ДО ВИКОНАННЯ (невиконання — штраф 50% балів):
    1. Програма повинна мати меню команд, виконання яких здійснювати через
       покажчики на функції.
    2. Пам'ять під масив слід виділяти та звільняти динамічно з використанням
       покажчиків.
    3. Передачу масивів у функції слід здійснювати через покажчики.
    4. Функції мають повертати масиви через покажчики.
    5. Доступ до елементів масивів здійснювати через покажчики.

  Як виконано кожну вимогу:
    1. Команди меню зібрано в масив структур MenuItem, кожна з яких містить
       поле-покажчик на функцію. Команда викликається через покажчик:
       (g_menu + choice - 1)->action() — жодного switch чи ланцюжка if для
       вибору команди немає.
    2. Масиви створюються оператором new[] і звільняються оператором
       delete[]; кожен масив адресується покажчиком.
    3. Усі функції обробки приймають масив параметром типу int * / const int *.
    4. Функції createArray(), copyArray(), squareNegatives() і sortDescending()
       повертають покажчик на новий масив.
    5. Звертання до елементів виконується через розіменування покажчика
       з адресною арифметикою: *(a + i) або рухомий покажчик *p, а не a[i].

  ПОСИЛАННЯ. Тема роботи охоплює обидва посилальні типи — покажчики і
  посилання. Масиви передаються та повертаються лише через покажчики, як
  вимагають пункти 3-5. Скалярні значення, які функція повертає додатково
  до основного результату (лічильники замінених елементів, обмінів, парних
  чисел), передаються через параметри-посилання виду int &. Це відповідає
  теоретичним відомостям до роботи: «щоб повертати з функцій більше одного
  значення, слід передати функції аргументи-посилання або аргументи-
  покажчики». Параметр-покажчик для скалярного значення має функція
  readInt(), тож в одній програмі є обидва способи:
      - параметр-покажчик: у виклику потрібна операція & взяття адреси,
        у тілі функції — розіменування *; покажчик може бути нульовим;
      - параметр-посилання: у виклику передається просто ім'я змінної,
        у тілі функції з ним працюють як зі звичайною змінною; посилання
        завжди зв'язане зі своєю змінною.

  ОБМЕЖЕННЯ УМОВИ: забороняється використовувати STL, методи класів колекцій
  (сортування, пошуку та інші).

  Виконав: Одарчук Олексій, КНУ імені Тараса Шевченка, ФІТ, група ІПЗ-11.

  Компілятор: g++ -std=c++17
==============================================================================*/

#include <cstdio>
#include <cstdlib>
#include <ctime>

/* Найбільше за модулем значення елемента масиву. Обмеження потрібне, щоб
   квадрат елемента вміщався в тип int: 30000^2 = 9*10^8, тоді як межа int
   близько 2,1*10^9. Застосовується однаково до введення з клавіатури та до
   генерації псевдовипадкових чисел. */
const int MAX_ABS_VALUE = 30000;

/*==============================================================================
  Функції введення
==============================================================================*/

/*------------------------------------------------------------------------------
  readInt — прочитати ціле число із заданого діапазону з контролем введення.

  Параметри:
      prompt [вхідний]  — текст запрошення;
      value  [вихідний] — ПОКАЖЧИК на змінну, куди записується число;
      low, high [вхідні] — межі припустимого діапазону.
  Повертає : true — число прочитано; false — вхідні дані вичерпано.
------------------------------------------------------------------------------*/
bool readInt(const char *prompt, int *value, int low, int high)
{
    for (;;) {
        printf("%s", prompt);
        const int scanned = scanf("%d", value);

        if (scanned == EOF)
            return false;
        if (scanned == 1 && *value >= low && *value <= high)
            return true;

        if (scanned != 1) {
            int c;
            /* очищення буфера */
            while ((c = getchar()) != '\n' && c != EOF) {
            }
        }
        printf("Помилка: потрібне ціле число від %d до %d.\n", low, high);
    }
}

/*==============================================================================
  Функції обробки масивів.
  Усі приймають масив через покажчик і повертають новий масив через покажчик.
==============================================================================*/

/*------------------------------------------------------------------------------
  createArray — створити масив у динамічній пам'яті та заповнити його.

  Параметри:
      n      [вхідний] — кількість елементів;
      byHand [вхідний] — true: введення з клавіатури; false: генерація;
      low, high [вхідні] — межі діапазону для генерації.

  Повертає: покажчик на створений масив або nullptr, якщо введення перервано.
            Пам'ять виділяється оператором new[]; звільняти її має викличний код.

  Локальні змінні:
      array — покажчик на початок виділеної ділянки пам'яті;
      p     — рухомий покажчик для заповнення елементів.
------------------------------------------------------------------------------*/
int *createArray(int n, bool byHand, int low, int high)
{
    int *const array = new int[n];

    if (byHand) {
        printf("Уведіть цілі числа від %d до %d (усього %d):\n", -MAX_ABS_VALUE,
               MAX_ABS_VALUE, n);

        for (int *p = array; p < array + n; ++p) {
            char prompt[40];
            snprintf(prompt, sizeof prompt, "  a[%ld] = ", (long)(p - array));

            if (!readInt(prompt, p, -MAX_ABS_VALUE, MAX_ABS_VALUE)) {
                delete[] array; /* звільнити пам'ять при перериванні */
                return nullptr;
            }
        }
    } else {
        const int range = high - low + 1;

        /* Масштабування rand() на діапазон замість rand() % range: стандарт гарантує
           лише RAND_MAX >= 32767, і для ширшого діапазону остача не охопила б
           усіх значень. */
        for (int *p = array; p < array + n; ++p)
            *p = low + (int)((double)rand() / ((double)RAND_MAX + 1.0) * range);
    }

    return array;
}

/*------------------------------------------------------------------------------
  copyArray — створити копію масиву в новій ділянці динамічної пам'яті.

  Параметри:
      source [вхідний] — покажчик на вихідний масив;
      n      [вхідний] — кількість елементів.
  Повертає : покажчик на новий масив-копію.
------------------------------------------------------------------------------*/
int *copyArray(const int *source, int n)
{
    int *const result = new int[n];

    for (int i = 0; i < n; ++i)
        *(result + i) = *(source + i);

    return result;
}

/*------------------------------------------------------------------------------
  printArray — вивести масив на екран через покажчик.

  Параметри:
      title [вхідний] — заголовок;
      a     [вхідний] — покажчик на масив;
      n     [вхідний] — кількість елементів.
------------------------------------------------------------------------------*/
void printArray(const char *title, const int *a, int n)
{
    printf("%s", title);

    for (const int *p = a; p < a + n; ++p)
        printf(" %d", *p);

    printf("\n");
}

/*------------------------------------------------------------------------------
  squareNegatives — побудувати новий масив, у якому від'ємні числа замінено
                    їх квадратами.

  Вихідний масив не змінюється: умова вимагає вивести УСІ перетворення
  вхідного масиву, тому кожне перетворення утворює окремий масив.

  Параметри:
      source  [вхідний]  — покажчик на вихідний масив;
      n       [вхідний]  — кількість елементів;
      changed [вихідний] — ПОСИЛАННЯ на лічильник замінених елементів.

  Масив повертається через покажчик (цього вимагає умова), а окреме скалярне
  значення — кількість замінених елементів — повертається через параметр-
  посилання. Посилання є альтернативним іменем змінної викличного коду, тому
  всередині функції з ним працюють як зі звичайною змінною: без операції
  розіменування та без ризику отримати неініціалізовану адресу.

  Квадрат обчислюється в типі int, тому має вміщатися в його межі. Для цього
  значення елементів обмежено за модулем числом 30000 під час створення
  масиву: 30000^2 = 9*10^8 менше за межу int (близько 2,1*10^9).
------------------------------------------------------------------------------*/
int *squareNegatives(const int *source, int n, int &changed)
{
    int *result;

    result = new int[n];
    changed = 0;

    for (int i = 0; i < n; ++i) {
        const int value = *(source + i);

        if (value < 0) {
            *(result + i) = value * value;
            ++changed;
        } else {
            *(result + i) = value;
        }
    }

    return result;
}

/*------------------------------------------------------------------------------
  sortDescending — побудувати новий масив, упорядкований за спаданням.

  Використано сортування вибором: на кожному кроці серед елементів, що
  лишилися невпорядкованими, знаходиться найбільший і переставляється на
  початок невпорядкованої частини. Бібліотечні методи сортування заборонені
  умовою, тому алгоритм реалізовано власними засобами.

  Параметри:
      source [вхідний]  — покажчик на вихідний масив;
      n      [вхідний]  — кількість елементів;
      swaps  [вихідний] — ПОСИЛАННЯ на лічильник обмінів.
  Повертає : покажчик на новий упорядкований масив.

  Локальні змінні:
      result  — покажчик на новий масив;
      maxPos  — індекс найбільшого елемента невпорядкованої частини;
      temp    — проміжне значення для обміну.
------------------------------------------------------------------------------*/
int *sortDescending(const int *source, int n, int &swaps)
{
    int *result;

    result = copyArray(source, n);
    swaps = 0;

    for (int i = 0; i < n - 1; ++i) {
        int maxPos = i;

        for (int j = i + 1; j < n; ++j)
            if (*(result + j) > *(result + maxPos))
                maxPos = j;

        if (maxPos != i) {
            const int temp = *(result + i);
            *(result + i) = *(result + maxPos);
            *(result + maxPos) = temp;
            ++swaps;
        }
    }

    return result;
}

/*------------------------------------------------------------------------------
  sumOfEven — сума парних чисел масиву.

  Парність визначається за остачею від ділення на 2. Для від'ємних чисел
  операція % у мові C++ може дати остачу -1, тому перевіряється рівність
  остачі нулю, а не одиниці — цей запис однаково правильний для обох знаків.

  Параметри:
      a     [вхідний]  — покажчик на масив;
      n     [вхідний]  — кількість елементів;
      count [вихідний] — ПОСИЛАННЯ на лічильник парних чисел.
  Повертає : сума парних чисел.

  Функція повертає ДВА значення: суму — як результат, кількість — через
  параметр-посилання. Це і є те застосування посилальних типів, про яке
  йдеться в теоретичних відомостях: «щоб повертати з функцій більше одного
  значення, слід передати функції аргументи-посилання або аргументи-покажчики».
------------------------------------------------------------------------------*/
long long sumOfEven(const int *a, int n, int &count)
{
    long long sum = 0;
    count = 0;

    for (const int *p = a; p < a + n; ++p)
        if (*p % 2 == 0) {
            sum += *p;
            ++count;
        }

    return sum;
}

/*==============================================================================
  Стан програми та команди меню.

  Кожна команда має однаковий заголовок, що дозволяє зберігати покажчики на
  них в одному масиві та викликати за індексом — це і є диспетчеризація
  через покажчики на функції.
==============================================================================*/

/* Поточний масив та його довжина. Оголошені глобально, щоб усі команди меню
   мали однаковий заголовок і могли зберігатися в спільному масиві покажчиків
   на функції. */
int *g_array = nullptr;
int g_size = 0;

/*------------------------------------------------------------------------------
  cmdCreate — команда меню: створити масив.
  Параметри: немає. Змінює глобальний стан g_array, g_size.
------------------------------------------------------------------------------*/
void cmdCreate(void)
{
    int n = 0;
    if (!readInt("Уведіть кількість елементів масиву (1..1000): ", &n, 1, 1000))
        return;

    printf("Спосіб створення:\n"
           "  1 - введення з клавіатури\n"
           "  2 - генерація псевдовипадкових чисел\n");

    int choice = 0;
    if (!readInt("Оберіть спосіб (1..2): ", &choice, 1, 2))
        return;

    int low = -100, high = 100;
    if (choice == 2) {
        if (!readInt("Уведіть нижню межу (від'ємну, не менше -30000): ", &low,
                     -MAX_ABS_VALUE, 0))
            return;
        if (!readInt("Уведіть верхню межу (додатну, не більше 30000): ", &high, 1,
                     MAX_ABS_VALUE))
            return;
        srand((unsigned)time(nullptr));
    }

    int *const created = createArray(n, choice == 1, low, high);
    if (created == nullptr)
        return;

    delete[] g_array; /* звільнити попередній масив */
    g_array = created;
    g_size = n;

    printf("\nМасив створено.\n");
    printArray("Вхідний масив:", g_array, g_size);
}

/*------------------------------------------------------------------------------
  cmdPrint — команда меню: вивести поточний масив.
------------------------------------------------------------------------------*/
void cmdPrint(void)
{
    if (g_array == nullptr) {
        printf("Масив ще не створено. Скористайтеся командою 1.\n");
        return;
    }
    printArray("Вхідний масив:", g_array, g_size);
}

/*------------------------------------------------------------------------------
  cmdSquareNegatives — команда меню: замінити від'ємні числа їх квадратами.
------------------------------------------------------------------------------*/
void cmdSquareNegatives(void)
{
    if (g_array == nullptr) {
        printf("Масив ще не створено. Скористайтеся командою 1.\n");
        return;
    }

    int changed = 0;
    int *const result = squareNegatives(g_array, g_size, changed);

    printArray("Вхідний масив:                   ", g_array, g_size);
    printArray("Від'ємні замінено їх квадратами: ", result, g_size);
    printf("Замінено елементів: %d\n", changed);

    delete[] result; /* тимчасовий масив більше не потрібен */
}

/*------------------------------------------------------------------------------
  cmdSortDescending — команда меню: упорядкувати масив за спаданням.
------------------------------------------------------------------------------*/
void cmdSortDescending(void)
{
    if (g_array == nullptr) {
        printf("Масив ще не створено. Скористайтеся командою 1.\n");
        return;
    }

    int swaps = 0;
    int *const result = sortDescending(g_array, g_size, swaps);

    printArray("Вхідний масив:            ", g_array, g_size);
    printArray("Упорядковано за спаданням:", result, g_size);
    printf("Виконано обмінів: %d\n", swaps);

    delete[] result;
}

/*------------------------------------------------------------------------------
  cmdSumOfEven — команда меню: визначити суму парних чисел.
------------------------------------------------------------------------------*/
void cmdSumOfEven(void)
{
    if (g_array == nullptr) {
        printf("Масив ще не створено. Скористайтеся командою 1.\n");
        return;
    }

    int count = 0;
    const long long sum = sumOfEven(g_array, g_size, count);

    printArray("Вхідний масив:", g_array, g_size);
    printf("Парних чисел: %d\n", count);
    printf("Сума парних чисел: %lld\n", sum);

    if (count == 0)
        printf("Парних чисел у масиві немає, тому сума дорівнює нулю.\n");
}

/*------------------------------------------------------------------------------
  cmdAll — команда меню: виконати всі перетворення послідовно й вивести їх.
           Відповідає вимозі умови «вивести на екран усі перетворення
           вхідного масиву».
------------------------------------------------------------------------------*/
void cmdAll(void)
{
    if (g_array == nullptr) {
        printf("Масив ще не створено. Скористайтеся командою 1.\n");
        return;
    }

    int changed = 0, swaps = 0, count = 0;

    int *const squared = squareNegatives(g_array, g_size, changed);
    int *const sorted = sortDescending(squared, g_size, swaps);
    const long long sum = sumOfEven(sorted, g_size, count);

    printf("\nУсі перетворення вхідного масиву\n");
    printArray("  1. Вхідний масив:                       ", g_array, g_size);
    printArray("  2. Від'ємні замінено їх квадратами:     ", squared, g_size);
    printArray("  3. Упорядковано за спаданням:           ", sorted, g_size);
    printf("  4. Сума парних чисел упорядкованого масиву: %lld "
           "(парних чисел: %d)\n",
           sum, count);
    printf("\n  Замінено від'ємних: %d, обмінів під час сортування: %d\n", changed,
           swaps);

    delete[] sorted;
    delete[] squared;
}

/*------------------------------------------------------------------------------
  cmdExit — команда меню: завершення роботи. Тіло порожнє: саме завершення
            виконує головний цикл, а команда потрібна для однорідності меню.
------------------------------------------------------------------------------*/
void cmdExit(void)
{
}

/*==============================================================================
  Опис меню: назва пункту та ПОКАЖЧИК НА ФУНКЦІЮ, що його виконує.
==============================================================================*/

/* Тип «покажчик на функцію без параметрів, яка не повертає значення». */
typedef void (*MenuAction)(void);

/* Пункт меню: текст і покажчик на функцію-обробник. */
typedef struct {
    const char *title;
    MenuAction action;
} MenuItem;

/* Таблиця команд. Потрібний пункт знаходиться зсувом покажчика на початок
   таблиці, без операторів switch чи ланцюжків if. */
const MenuItem g_menu[] = {
    {"створити масив", cmdCreate},
    {"вивести масив", cmdPrint},
    {"замінити від'ємні числа їх квадратами", cmdSquareNegatives},
    {"упорядкувати масив за спаданням", cmdSortDescending},
    {"визначити суму парних чисел", cmdSumOfEven},
    {"виконати та вивести всі перетворення", cmdAll},
    {"вихід", cmdExit}};

const int MENU_SIZE = (int)(sizeof g_menu / sizeof g_menu[0]);

/*------------------------------------------------------------------------------
  Головна функція. Відображає меню та викликає обрану команду через
  покажчик на функцію.

  Локальні змінні:
      choice — номер обраного пункту меню.
------------------------------------------------------------------------------*/
int main(void)
{
    printf("Лабораторна робота №8 (варіант 19)\n");
    printf("Виконав: студент групи ІПЗ-11 Одарчук Олексій\n");
    printf("Обробка одновимірного масиву\n");

    for (;;) {
        printf("\n============================================================\n");
        printf("Меню команд:\n");

        for (int i = 0; i < MENU_SIZE; ++i)
            printf("  %d - %s\n", i + 1, (g_menu + i)->title);

        int choice = 0;
        char prompt[40];
        snprintf(prompt, sizeof prompt, "Оберіть команду (1..%d): ", MENU_SIZE);
        if (!readInt(prompt, &choice, 1, MENU_SIZE)) {
            printf("\nВхідні дані вичерпано. Завершення роботи.\n");
            break;
        }

        printf("\n");

        /* Виклик команди через покажчик на функцію. */
        (g_menu + choice - 1)->action();

        if ((g_menu + choice - 1)->action == cmdExit) {
            printf("Завершення роботи.\n");
            break;
        }
    }

    /* Звільнення динамічної пам'яті перед виходом. */
    delete[] g_array;
    g_array = nullptr;

    return 0;
}
