/*==============================================================================
  Лабораторна робота №7. Завдання 2. Варіант 19.
  Тема: алгоритми матричної алгебри.

  Умова (таблиця 7.1, завдання 19.2): нехай n підприємств випускають m видів
  продукції. Матриця A = (a[i][j]), i = 1..n, j = 1..m задає обсяги i-ої
  продукції на j-му підприємстві в першому кварталі, а матриця
  B = (b[i][j]) — у другому кварталі. Знайти:
    а) сумарний обсяг продукції за два квартали по двох підприємствах;
    б) приріст або зменшення обсягів продукції за другий квартал порівняно
       з першим за видами продукції та по підприємствах.
  Вимірності матриць увести з клавіатури, значення елементів матриць
  генерувати або вводити з клавіатури на вимогу користувача. Вивести
  результати за пунктами а) та б) з відповідними повідомленнями.

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

  ЗАУВАЖЕННЯ ЩОДО ІНДЕКСАЦІЇ. Вступне речення умови говорить про n підприємств
  і m видів продукції, а формула задає A = (a[i][j]) з i = 1..n, j = 1..m,
  де за текстом i — номер продукції, а j — номер підприємства. Прийнято
  трактування, задане текстом умови безпосередньо: РЯДОК матриці відповідає
  виду продукції, СТОВПЕЦЬ — підприємству. Отже n — кількість видів продукції
  (рядків), m — кількість підприємств (стовпців). Заголовки таблиць
  побудовано відповідно, тому неоднозначність умови не впливає на читання
  результатів.

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

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

  Компілятор: gcc -std=c17
==============================================================================*/

#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <time.h>

/* Найбільші припустимі вимірності матриць. */
const int MAX_ROWS = 20; /* видів продукції   */
const int MAX_COLS = 20; /* підприємств       */

/*------------------------------------------------------------------------------
  utf8Width — ширина рядка в символах, а не в байтах.

  У кодуванні UTF-8 кирилична літера займає два байти, тому специфікатор
  формату виду %-16s, який рахує байти, вирівнює заголовки таблиць
  неправильно. Функція рахує лише початкові байти символів: у продовжувальних
  байтах старші біти дорівнюють 10 у двійковій системі.

  Параметри: s [вхідний] — рядок у кодуванні UTF-8.
  Повертає : кількість символів рядка.
------------------------------------------------------------------------------*/
int utf8Width(const char *s)
{
    int width = 0;

    for (const unsigned char *p = (const unsigned char *)s; *p != '\0'; ++p)
        /* Продовжувальний байт UTF-8 має вигляд 10xxxxxx: маска 0xC0 лишає
           два старші біти, і якщо вони не дорівнюють 10, це початок символу. */
        if ((*p & 0xC0) != 0x80)
            ++width;

    return width;
}

/*------------------------------------------------------------------------------
  printPadded — вивести рядок, доповнивши його пропусками до заданої ширини.

  Параметри:
      s     [вхідний] — рядок, що виводиться;
      width [вхідний] — потрібна ширина поля в символах;
      left  [вхідний] — true: вирівнювання ліворуч; false: праворуч.

  Локальні змінні:
      pad — кількість пропусків, які треба додати.
------------------------------------------------------------------------------*/
void printPadded(const char *s, int width, bool left)
{
    const int pad = width - utf8Width(s);

    if (!left)
        for (int i = 0; i < pad; ++i)
            putchar(' ');

    printf("%s", s);

    if (left)
        for (int i = 0; i < pad; ++i)
            putchar(' ');
}

/*------------------------------------------------------------------------------
  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);
    }
}

/*------------------------------------------------------------------------------
  fillMatrix — заповнити матрицю обсягів продукції.

  Параметри:
      a    [вихідний] — матриця, що заповнюється;
      n    [вхідний]  — кількість рядків (видів продукції);
      m    [вхідний]  — кількість стовпців (підприємств);
      name [вхідний]  — назва матриці для запрошень;
      byHand [вхідний] — true: введення з клавіатури; false: генерація;
      low, high [вхідні] — межі діапазону для генерації.
  Повертає : true — заповнено; false — вхідні дані вичерпано.
------------------------------------------------------------------------------*/
bool fillMatrix(int a[][MAX_COLS], int n, int m, const char *name, bool byHand, int low,
                int high)
{
    if (byHand) {
        printf("Уведіть обсяги для матриці %s; рядок — вид продукції, "
               "стовпець — підприємство:\n",
               name);

        for (int i = 0; i < n; ++i)
            for (int j = 0; j < m; ++j) {
                char prompt[80];
                snprintf(prompt, sizeof prompt,
                         "  продукція %d, підприємство %d: ", i + 1, j + 1);
                if (!readInt(prompt, &a[i][j], 0, 1000000))
                    return false;
            }
    } else {
        /* Масштабування rand() на діапазон замість rand() % range: стандарт гарантує
           лише RAND_MAX >= 32767, і для ширшого діапазону остача не охопила б
           усіх значень. */
        for (int i = 0; i < n; ++i)
            for (int j = 0; j < m; ++j)
                a[i][j] = low + (int)((double)rand() / ((double)RAND_MAX + 1.0) *
                                      (high - low + 1));
    }
    return true;
}

/*------------------------------------------------------------------------------
  printSeparator — вивести розділювальну лінію таблиці.
  Параметри: width [вхідний] — довжина лінії в символах.
------------------------------------------------------------------------------*/
void printSeparator(int width)
{
    printf("  ");
    for (int i = 0; i < width; ++i)
        putchar('-');
    printf("\n");
}

/*------------------------------------------------------------------------------
  printMatrix — вивести матрицю обсягів у табличному вигляді із заголовками
                рядків (види продукції) та стовпців (підприємства).

  Праворуч виводяться підсумки рядків, унизу — підсумки стовпців.

  Параметри:
      title [вхідний] — заголовок таблиці;
      a     [вхідний] — матриця;
      n, m  [вхідні]  — кількість рядків і стовпців.

  ЗАУВАЖЕННЯ ЩОДО ПАРАМЕТРА-МАТРИЦІ. Параметр оголошено без модифікатора
  const, хоча функція матрицю не змінює. Причина суто мовна: у стандарті C
  до редакції C23 масив виду int a[N][M] НЕ перетворюється неявно на
  const int (*)[M] — на відміну від C++, де таке перетворення дозволене.
  Додавання const тут спричинило б попередження -Wpedantic.
------------------------------------------------------------------------------*/
void printMatrix(const char *title, int a[][MAX_COLS], int n, int m)
{
    printf("\n%s\n", title);

    printf("  ");
    printPadded("Продукція \\ П-во", 16, true);
    for (int j = 0; j < m; ++j)
        printf("%10d", j + 1);
    printPadded("Разом", 12, false);
    printf("\n");

    printSeparator(16 + 10 * m + 12);

    for (int i = 0; i < n; ++i) {
        printf("  Продукція %-6d", i + 1);

        long long rowTotal = 0;
        for (int j = 0; j < m; ++j) {
            printf("%10d", a[i][j]);
            rowTotal += a[i][j];
        }
        printf("%12lld\n", rowTotal);
    }

    printSeparator(16 + 10 * m + 12);
    printf("  ");
    printPadded("Разом", 16, true);

    long long grandTotal = 0;
    for (int j = 0; j < m; ++j) {
        long long columnTotal = 0;
        for (int i = 0; i < n; ++i)
            columnTotal += a[i][j];
        printf("%10lld", columnTotal);
        grandTotal += columnTotal;
    }
    printf("%12lld\n", grandTotal);
}

/*------------------------------------------------------------------------------
  addMatrices — поелементна сума двох матриць: c[i][j] = a[i][j] + b[i][j].

  Параметри:
      a, b [вхідні]   — доданки;
      c    [вихідний] — результат;
      n, m [вхідні]   — кількість рядків і стовпців.
------------------------------------------------------------------------------*/
void addMatrices(int a[][MAX_COLS], int b[][MAX_COLS], int c[][MAX_COLS], int n, int m)
{
    for (int i = 0; i < n; ++i)
        for (int j = 0; j < m; ++j)
            c[i][j] = a[i][j] + b[i][j];
}

/*------------------------------------------------------------------------------
  subtractMatrices — поелементна різниця двох матриць: c[i][j] = a[i][j] - b[i][j].

  Параметри:
      a    [вхідний]  — зменшуване;
      b    [вхідний]  — від'ємник;
      c    [вихідний] — результат;
      n, m [вхідні]   — кількість рядків і стовпців.
------------------------------------------------------------------------------*/
void subtractMatrices(int a[][MAX_COLS], int b[][MAX_COLS], int c[][MAX_COLS], int n,
                      int m)
{
    for (int i = 0; i < n; ++i)
        for (int j = 0; j < m; ++j)
            c[i][j] = a[i][j] - b[i][j];
}

/*------------------------------------------------------------------------------
  reportTotalsByPlant — пункт а) умови: сумарний обсяг продукції за два квартали
                        по кожному підприємству.

  Параметри:
      a, b [вхідні] — обсяги випуску за I та II квартали;
      n    [вхідний] — кількість видів продукції (рядків);
      m    [вхідний] — кількість підприємств (стовпців).

  Локальні змінні:
      totalByPlant — сумарний обсяг по кожному підприємству;
      grandTotal   — підсумок по всіх підприємствах.
------------------------------------------------------------------------------*/
void reportTotalsByPlant(int a[][MAX_COLS], int b[][MAX_COLS], int n, int m)
{
    long long totalByPlant[MAX_COLS];
    long long grandTotal = 0;

    for (int j = 0; j < m; ++j) {
        totalByPlant[j] = 0;
        for (int i = 0; i < n; ++i)
            totalByPlant[j] += (long long)a[i][j] + b[i][j];
        grandTotal += totalByPlant[j];
    }

    printf("\n============================================================\n");
    printf("а) Сумарний обсяг продукції за два квартали по підприємствах\n\n");
    printf("  ");
    printPadded("Підприємство", 18, true);
    printPadded("Обсяг за I+II кв.", 18, false);
    printf("\n  ------------------------------------\n");

    for (int j = 0; j < m; ++j)
        printf("  Підприємство %-5d%18lld\n", j + 1, totalByPlant[j]);

    printf("  ------------------------------------\n  ");
    printPadded("Разом", 18, true);
    printf("%18lld\n", grandTotal);
}

/*------------------------------------------------------------------------------
  reportChanges — пункт б) умови: приріст або зменшення обсягів за другий
                  квартал порівняно з першим, за видами продукції та по
                  підприємствах.

  Параметри:
      a, b [вхідні] — обсяги випуску за I та II квартали;
      n, m [вхідні] — кількість видів продукції та підприємств.

  Локальні змінні:
      deltaByProduct — зміна обсягу за кожним видом продукції;
      deltaByPlant   — зміна обсягу по кожному підприємству.
------------------------------------------------------------------------------*/
void reportChanges(int a[][MAX_COLS], int b[][MAX_COLS], int n, int m)
{
    long long deltaByProduct[MAX_ROWS];
    long long deltaByPlant[MAX_COLS];

    for (int i = 0; i < n; ++i) {
        deltaByProduct[i] = 0;
        for (int j = 0; j < m; ++j)
            deltaByProduct[i] += (long long)b[i][j] - a[i][j];
    }

    for (int j = 0; j < m; ++j) {
        deltaByPlant[j] = 0;
        for (int i = 0; i < n; ++i)
            deltaByPlant[j] += (long long)b[i][j] - a[i][j];
    }

    printf("\n============================================================\n");
    printf("б) Приріст (+) або зменшення (-) обсягів за II квартал\n");
    printf("   порівняно з I кварталом\n");

    printf("\n  За видами продукції:\n  ");
    printPadded("Вид продукції", 18, true);
    printPadded("Зміна обсягу", 14, false);
    printf("   Оцінка\n");
    printf("  --------------------------------------------------\n");

    for (int i = 0; i < n; ++i)
        printf("  Продукція %-8d%+14lld   %s\n", i + 1, deltaByProduct[i],
               deltaByProduct[i] > 0   ? "приріст"
               : deltaByProduct[i] < 0 ? "зменшення"
                                       : "без змін");

    printf("\n  По підприємствах:\n  ");
    printPadded("Підприємство", 18, true);
    printPadded("Зміна обсягу", 14, false);
    printf("   Оцінка\n");
    printf("  --------------------------------------------------\n");

    for (int j = 0; j < m; ++j)
        printf("  Підприємство %-5d%+14lld   %s\n", j + 1, deltaByPlant[j],
               deltaByPlant[j] > 0   ? "приріст"
               : deltaByPlant[j] < 0 ? "зменшення"
                                     : "без змін");
}

/*------------------------------------------------------------------------------
  Головна функція. Читає обидві матриці обсягів, обчислює сумарні обсяги
  за два квартали та приріст/зменшення за другий квартал.

  Локальні змінні:
      a, b       — обсяги продукції за перший та другий квартали;
      sum, diff  — проміжні матриці A + B та B - A;
      n, m       — кількість видів продукції та підприємств;
      byHand     — спосіб задання елементів матриць;
      low, high  — межі діапазону для генерації;
      verbose    — режим виведення: true — з проміжними обчисленнями.
------------------------------------------------------------------------------*/
int main()
{
    printf("Лабораторна робота №7, завдання 2 (варіант 19)\n");
    printf("Виконав: студент групи ІПЗ-11 Одарчук Олексій\n");
    printf("Обсяги випуску продукції за два квартали\n\n");
    printf("Рядок матриці — вид продукції, стовпець — підприємство.\n\n");

    int n = 0, m = 0;
    if (!readInt("Уведіть кількість видів продукції n (1..20): ", &n, 1, MAX_ROWS))
        return 1;
    if (!readInt("Уведіть кількість підприємств m (1..20):   ", &m, 1, MAX_COLS))
        return 1;

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

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

    const bool byHand = (choice == 1);
    int low = 0, high = 0;

    if (!byHand) {
        if (!readInt("Уведіть нижню межу обсягу: ", &low, 0, 1000000))
            return 1;
        if (!readInt("Уведіть верхню межу обсягу: ", &high, low, 1000000))
            return 1;
        srand((unsigned)time(NULL));
    }

    printf("\nРежим виведення результатів:\n"
           "  1 - тільки кінцевий результат\n"
           "  2 - з проміжними обчисленнями\n");

    if (!readInt("Оберіть режим (1..2): ", &choice, 1, 2))
        return 1;

    const bool verbose = (choice == 2);

    int a[MAX_ROWS][MAX_COLS], b[MAX_ROWS][MAX_COLS];
    if (!fillMatrix(a, n, m, "A (I квартал)", byHand, low, high))
        return 1;
    if (!fillMatrix(b, n, m, "B (II квартал)", byHand, low, high))
        return 1;

    printMatrix("Матриця A — обсяги випуску за I квартал", a, n, m);
    printMatrix("Матриця B — обсяги випуску за II квартал", b, n, m);

    if (verbose) {
        int sum[MAX_ROWS][MAX_COLS];
        addMatrices(a, b, sum, n, m);
        printMatrix("Проміжний результат: матриця A + B — обсяги за два квартали\n"
                    "(підсумки стовпців — сумарні обсяги по підприємствах)",
                    sum, n, m);
    }

    reportTotalsByPlant(a, b, n, m);

    if (verbose) {
        int diff[MAX_ROWS][MAX_COLS];
        subtractMatrices(b, a, diff, n, m);
        printMatrix("Проміжний результат: матриця B - A — зміна обсягів\n"
                    "(підсумки рядків — зміна за видами продукції, "
                    "підсумки стовпців — по підприємствах)",
                    diff, n, m);
    }

    reportChanges(a, b, n, m);

    return 0;
}
