#include <iostream> // подключение библиотеки ввода-вывода
#include <cmath> // подключение библиотеки для проведения математических операций

using namespace std; // подключение стандартного пространства имён

// объявление прототипов класса, для того, чтобы компилятор "увидел" класс и не было ошибок в области видимости
class Point;
class Line;

class Point {
    int x, y; // переменные x y, представляющие собой координаты точки

    public:

    void set_coordinates(int _x, int _y) { // дополнительный сеттер (с передачей значений через аргумент функции)
        this->x = _x; this->y = _y; // передача аргументов функции в качестве значений переменных класса
    }

    // геттеры
    int get_x() { return this->x; } // получение значения переменной x
    int get_y() { return this->y; } // получение значения переменной y

    friend istream& operator>> (istream &in, Line& line); // объявление оператора ввода дружественным для прямого доступа к приватным полям
    friend ostream& operator<< (ostream& out, Line& line); // объявление оператора вывода дружественным для прямого доступа к приватным полям
};

class Line {
    Point points[2]; // координаты точек прямой
    // статическая переменная объявляется с помощью inline, которая позволяет указать, что данная переменная встраимавая
    // без него компилятор выдаёт ошибку "неопределённая ссылка на функцию" и не позволяет скомпилировать программу
    static inline Point main_line_points[2]; // координаты точек главной прямой, объявленные статически согласно 3 заданию

    public:

    static Point* get_main_line_points() { return main_line_points; } // получение значений координат главной прямой

    static void set_main_line_points(int _x[2], int _y[2]) { // установка координат главной прямой
        main_line_points[0].set_coordinates(_x[0], _y[0]); // установка координат 1 точки
        main_line_points[1].set_coordinates(_x[1], _y[1]); // установка координат 2 точки
    }

    Line(){ // конструктор без параметров
        cout << "\n\t\t\t\t\t\t\t\t\t\t\tConstructor " << this << " initialized.\n"; // вывод информации о инициализации конструктора
    }

    Line(int _x[2], int _y[2]){ // конструктор с параметрами
        cout << "\n\t\t\t\t\t\t\t\t\t\t\tConstructor " << this << " initialized.\n"; // вывод информации о инициализации конструктора
        // присваивание точкам значений, введённых пользователем
        this->points[0].set_coordinates(_x[0], _y[0]); this->points[1].set_coordinates(_x[1], _y[1]);
    }
/*
    Line(int _x1 = 1, int _x2 = 10, int _y1 = 1, int _y2 = 10){ // конструктор с параметрами по умолчанию
        cout << "\n\t\t\t\t\t\t\t\t\t\t\tConstructor " << this << " initialized.\n"; // вывод информации о инициализации конструктора
        // присваивание точкам значений, введённых пользователем
        this->points[0].set_coordinates(_x1, _y1); this->points[1].set_coordinates(_x2, _y2);
    }
*/
    ~Line(){ // деструктор
        cout << "\n\t\t\t\t\t\t\t\t\t\t\tDestructor " << this << " initialized.\n"; // вывод информации о инициализации деструктора
    }


    bool is_line_parallel() { // проверка прямой на параллельность относительно главной прямой
        // вычисление угловых коэффициентов текущей и основной прямых по формуле: (y2 - y1) / (x2 - x1) = result
        float line_divisible = (this->points[1].get_y() - this->points[0].get_y()); // делимое прямой
        float line_divider = (this->points[1].get_x() - this->points[0].get_x()); // делитель прямой

        float main_line_divisible = (main_line_points[1].get_y() - main_line_points[0].get_y()); // делимое главной прямой
        float main_line_divider = (main_line_points[1].get_x() - main_line_points[0].get_x()); // делитель главной прямой
        
        float line_angular_coefficient = (line_divisible / line_divider); // угловой коэффициент прямой
        float main_line_angular_coefficient = (main_line_divisible/main_line_divider); // угловой коэффициент главной прямой
        
        return (line_angular_coefficient == main_line_angular_coefficient); // если угловые коэффициенты равны, то прямые параллельны
    }

    void set_line_to_gorisontal_parallel() { // установка прямой в параллельное положение относительно горизонтальной оси (абцисс aka x)
        if (this->points[0].get_y() != this->points[1].get_y()){ // если координаты точек прямой по оси ординат (y) не совпадают
            this->points[1].set_coordinates(this->points[1].get_x(), this->points[0].get_y()); // присваиваем координате y 2 точки значение y 1 точки
        }
    }

    bool operator == (Line& line){ // проверка на параллельность через перегрузку оператора сравнения
        return line.is_line_parallel();
    }

    friend istream& operator>> (istream &in, Line& line); // объявление оператора ввода дружественным для прямого доступа к приватным полям
    friend ostream& operator<< (ostream& out, Line& line); // объявление оператора вывода дружественным для прямого доступа к приватным полям
};

istream& operator>> (istream &in, Line& line){ // перегрузка оператора ввода для прямого ввода координат точек
    cout << "\tInput 1 point x and y coordinates (use space to split them): "; in >> line.points[0].x >> line.points[0].y; // ввод 1 точки
    cout << "\tInput 2 point x and y coordinates (use space to split them): "; in >> line.points[1].x >> line.points[1].y; // ввод 2 точки
    return in;
}

ostream& operator<< (ostream& out, Line& line){ // перегрузка оператора вывода для вывода значений координат точек
    return out 
    << "\t1 point's x y coordinates: " << line.points[0].x << " " << line.points[0].y // вывод координат 1 точки
    << "\n\t2 point's x y coordinates: " << line.points[1].x << " " << line.points[1].y << "\n"; // // вывод координат 2 точки
}

ostream& operator<< (ostream& out, Point* point){ // перегрузка оператора вывода для вывода значений координат точек главной линии
    return out 
    << "\t1 point's x y coordinates: " << point[0].get_x() << " " << point[0].get_y() // вывод координат 1 точки
    << "\n\t2 point's x y coordinates: " << point[1].get_x() << " " << point[1].get_y() << "\n"; // // вывод координат 2 точки
}

void set_main_line(){ // функция установки значений главной линии
    int x[2], y[2]; // объявление двух массивов x и y для соответсвующих координат точек
    cout << "\nSet main line:\n";
    cout << "\tInput 1 point x and y coordinates (use space to split them): "; cin >> x[0] >> y[0]; // ввод 1 точки
    cout << "\tInput 2 point x and y coordinates (use space to split them): "; cin >> x[1] >> y[1]; // ввод 2 точки
    Line::set_main_line_points(x, y); // передача введённых значений в статическую функцию установки значений статической переменной
}

void check_parallel(Line* lines[], int lines_count){ // функция проверки прямых на параллельность главной прямой
    bool count = false; // счётчик наличия параллельных линий
    cout << "\nDefined parallel lines:\n";
    for (int i = 0; i < lines_count; i++){ // цикл на проверку всех введённых прямых
        if ((*lines[i]) == (*lines[i])){ // проверка на параллельность через перегрузку оператора сравнения
            cout << "\t" << i+1 << " line is parallel to the main line.\n"; // вывод о параллельности прямой
            (*lines[i]).set_line_to_gorisontal_parallel(); // изменение прямой для соответствия горизонтальной оси
            count = true; // изменение счётчика на true
        }
    }
    // если счётчик false, то параллельных прямых не обнаружено
    if (!count) cout << "\tThere are no parallel lines.\n";
}

enum UserChoice { // список возможных выборов пользователя
    DEFAULT = 1, // конструктор по умолчанию (без параметров)
    WITH_PARAMETERS = 2, // конструктор с параметрами
};

int main(){
    int lines_count; // общее количество прямых
    int choice; // выбор типа конструктора (без параметров, с параметрами)

    cout << "Input lines count: "; cin >> lines_count; // ввод количества

    if (lines_count < 1){ // если количество < 1, то
        cout << "Lines count can't be less than 1.\n"; // вывод сообщения об ошибке
        return 0; // завершение программы
    }

    // вывод возможных вариантов для инициализации объектов
    cout << "\nPlease, choose constructor's type:" << "\n";
    cout << "1. Default constructor (without parameters)." << "\n";
    cout << "2. Constructor with parameters." << "\n";
    // ввод пользователя
    cin >> choice;

    switch (choice)
    {
    case UserChoice::DEFAULT:{
        set_main_line(); // функция ввода главной прямой, с которой будут сравниваться все остальные прямые

        Line* lines[lines_count]; // массив прямых

        for (int i = 0; i < lines_count; i++){ // проход по массиву
            lines[i] = new Line(); // инициализация нового экземпляра класса через явный конструктор без параметров
            cout << "Input " << i+1 << " line:\n";
            cin >> (*lines[i]); // ввод значений координат через перегрузку оператора ввода
        }

        check_parallel(lines, lines_count); // функция проверки прямых на параллельность относительно главной прямой

        cout << "\nOutput after check\n\n"; // вывод после проверки

        cout << "Main line coordinates:\n" << Line::get_main_line_points(); // вывод координат главной прямой

        for (int i = 0; i < lines_count; i++) cout << i+1 << " line:\n" << (*lines[i]); // вывод координат прямых

        for (int i = 0; i < lines_count; i++) delete lines[i]; // удаление динамических экземпляров класса

        break; 
    }
    case UserChoice::WITH_PARAMETERS:{
        set_main_line(); // функция ввода главной прямой, с которой будут сравниваться все остальные прямые

        Line* lines[lines_count]; // массив прямых

        for (int i = 0; i < lines_count; i++){ // проход по массиву
            int x[2], y[2]; // объявление двух массивов x и y для соответсвующих координат точек
            cout << "Input " << i+1 << " line:\n";
            for (int j = 0; j < 2; j++) {
                cout << "\tInput " << j+1 << " point x and y coordinates (use space to split them): "; cin >> x[j] >> y[j]; // ввод точки
            }
            lines[i] = new Line(x, y); // инициализация нового экземпляра класса через явный конструктор с параметрами
        }

        check_parallel(lines, lines_count); // функция проверки прямых на параллельность относительно главной прямой

        cout << "\nOutput after check\n\n"; // вывод после проверки

        cout << "Main line coordinates:\n" << Line::get_main_line_points(); // вывод координат главной прямой

        for (int i = 0; i < lines_count; i++) cout << i+1 << " line:\n" << (*lines[i]); // вывод координат прямых

        for (int i = 0; i < lines_count; i++) delete lines[i]; // удаление динамических экземпляров класса

        break;
    }
    default: // если ничего не выбрано
        cout << "\n" << "Incorrect input." << "\n\n"; // вывод о некорректном вводе
        return 0; // завершение программы
    }

    return 0; // завершение программы
}