"""
Класс страница параметров создания wizard
"""
import traceback
from math import ceil, log10
from typing import Optional

from PySide2.QtCore import Qt, Slot, QRegExp, QEvent, Signal
from PySide2.QtGui import QRegExpValidator
from PySide2.QtWidgets import QWizardPage, QVBoxLayout, QLabel, QPushButton, QLineEdit, QCheckBox, QComboBox, QWidget, \
    QFormLayout, QWizard, QDialog, QMessageBox, QProgressBar
from osgeo import gdal

from axipy import CoordSystem, ChooseCoordSystemDialog, tr, Pnt, AxipyAnyCallableTask, task_manager, CoordTransformer
from .. import helper
from .. import wizardconverter
from ..helper import debug
from ..json_pkg import jsonloader


class CreationPage(QWizardPage):
    """
    Страница выбора выходной СК, параметра перепроецирования СК, выбора выходного расширения
    и параметров создания выходных файлов. Реализован автоматический расчет охвата для файлов MapInfo.
    Параметр перепроецирования доступен, если входная и выходная СК не равны.
    """

    lnd_clicked = Signal()

    def __init__(self, wizard) -> None:
        super().__init__()

        self.wzrd = wizard  # type: wizardconverter.WizardConverter

        width = self.wzrd.wizard_width * 35 // 100
        # Вертикальная разметка страницы
        self.vbox = QVBoxLayout()
        self.setLayout(self.vbox)

        label = QLabel(tr("Формат выходных файлов"))
        label.setStyleSheet("font-weight: bold")
        self.out_format_label = QLabel()
        self.vbox.addWidget(label)
        self.vbox.addWidget(self.out_format_label)

        label = QLabel(tr("Режим создания файлов"))
        label.setFixedWidth(width)
        self.cbox_file_crtn_mode = QComboBox()
        self.cbox_file_crtn_mode.currentTextChanged.connect(self.cbox_show_file_name_field)

        hbox = helper.create_hbox(label, self.cbox_file_crtn_mode)
        self.vbox.addLayout(hbox)
        label = QLabel(tr("Имя файла"))
        label.setFixedWidth(width)
        self.lnd_append_file_name = QLineEdit()
        hbox = helper.create_hbox(label, self.lnd_append_file_name)
        hbox.setContentsMargins(0, 0, 0, 0)
        self.file_name_for_append = QWidget()
        self.file_name_for_append.setLayout(hbox)
        self.vbox.addWidget(self.file_name_for_append)

        # Заголовок
        title_label = QLabel(tr("Выходная система координат"))
        title_label.setStyleSheet("font-weight: bold")
        self.vbox.addWidget(title_label)
        # Отображение и выбор СК
        self.line_edit = QLineEdit()
        self.line_edit.setReadOnly(True)
        btn = QPushButton("...", clicked=self.choose_out_coord, objectName="btn_choose_out_coord")
        btn.setMaximumWidth(20)
        hbox = helper.create_hbox(self.line_edit, btn)
        hbox.setAlignment(Qt.AlignLeft)
        self.vbox.addLayout(hbox)
        # Перепроецирование СК
        self.label = QLabel(tr("Перепроецировать координаты"))
        self.chbox_reproj = QCheckBox()
        self.chbox_reproj.stateChanged.connect(self.chbox_reproj_state_changed)
        hbox = helper.create_hbox(self.label, self.chbox_reproj)
        hbox.setAlignment(Qt.AlignLeft)
        self.vbox.addLayout(hbox)

        # Выбор выходного расширения
        label_out_ext = QLabel(tr("Выходное расширение"))
        label_out_ext.setFixedWidth(width)
        self.cbox_out_ext = QComboBox(objectName="cbox_out_ext")
        self.cbox_out_ext.setEditable(True)
        hbox = helper.create_hbox(label_out_ext, self.cbox_out_ext)
        hbox.setAlignment(Qt.AlignLeft)
        hbox.setContentsMargins(0, 0, 0, 0)
        self.widget_out_ext = QWidget()
        self.widget_out_ext.setLayout(hbox)
        self.vbox.addWidget(self.widget_out_ext)

        self.form_view = None  # Форма с параметрами создания

        self.pbar = QProgressBar()
        self.pbar.hide()
        self.vbox.addWidget(self.pbar)

        # Расчет охвата для выходного драйвера MapInfo File
        self.line_edit_bounds = None
        self.lnd_bounds_last_text = None
        self.btn_widget = None
        self.m_box = QMessageBox(
            QMessageBox.Question, tr("Вопрос"),
            tr("Отменить расчет охвата?"),
            buttons=QMessageBox.Yes | QMessageBox.No, parent=self)

        self.task = None

    def initializePage(self) -> None:
        """
        Инициализация страницы.
        """

        # Инициализация надписи выходной формат
        self.out_format_label.setText("{}".format(self.wzrd.drivers[self.wzrd.out_gdal_format]["name"]))

        self.initialize_creation_modes()
        self.initialize_cs()
        self.initialize_out_ext()

        try:
            # Инициализация формы с настройками
            self.form_view = jsonloader.get_form_view(self, self.wzrd.out_gdal_format, ["dataset", "layer"])
            # Вставка на одну позицию выше полоски прогресса
            i = self.vbox.indexOf(self.findChild(QProgressBar))
            self.vbox.insertWidget(i, self.form_view)
        except (Exception,):
            if debug:
                traceback.print_exc()
            helper.notify(tr("Не удалось создать форму с параметрами создания"))

        # Добавление кнопки рассчета охвата для выходного формата "MapInfo File"
        if self.wzrd.out_gdal_format == "MapInfo File":
            # Кнопка
            btn_calc_bounds = QPushButton(tr("Рассчитать охват"))
            btn_calc_bounds.clicked.connect(self.calc_bounds_start_task)
            btn_calc_bounds.setToolTip(tr("Рассчет охвата с повышенной точностью."))

            btn_get_cs_bound = QPushButton(tr("Охват выходной СК"))
            btn_get_cs_bound.clicked.connect(self.validate_bounds)

            btn_layout = helper.create_hbox(btn_calc_bounds, btn_get_cs_bound)
            btn_layout.setContentsMargins(0, 0, 0, 0)
            self.btn_widget = QWidget()
            self.btn_widget.setLayout(btn_layout)

            # Получение позиции для кнопки
            form = self.findChild(QFormLayout)  # type: QFormLayout
            name = str(jsonloader.get_indx_from_type("layer")) + "BOUNDS"
            self.line_edit_bounds = self.findChild(QLineEdit, name)
            self.lnd_bounds_last_text = ""
            row = form.getWidgetPosition(self.line_edit_bounds)[0]
            # Вставка кнопки на позицию ниже BOUNDS
            form.insertRow(row + 1, QLabel(""), self.btn_widget)

            # не ставить выше инициализации формы
            self.validate_bounds()

        # не ставить выше инициализации формы
        self.validate_check_box()

        self.setButtonText(QWizard.NextButton, tr("Конвертировать"))
        self.pbar.hide()

        if self.wzrd.auto_rect_needed and self.wzrd.out_gdal_format == "MapInfo File":
            self.calc_bounds_start_task()


    def initialize_creation_modes(self) -> None:
        # Режим создания файлов
        # Инициализация режима создания файлов (Сброс параметров).
        modes = self.wzrd.file_creation_modes
        self.cbox_file_crtn_mode.clear()
        self.lnd_append_file_name.clear()
        self.wzrd.file_name_for_append = None
        # Входной формат multilayer
        inp_m = self.wzrd.drivers[self.wzrd.inp_gdal_format].get("multilayer_inp", False)
        # Выходной формат multilayer
        out_m = self.wzrd.drivers[self.wzrd.out_gdal_format].get("multilayer_out", False)
        # Выходной формат поддерживает append
        out_a = self.wzrd.drivers[self.wzrd.out_gdal_format].get("append", False)
        # Добавление режимов в combobox
        self.cbox_file_crtn_mode.addItem(modes["one_to_one"])  # Каждую таблицу в отдельный файл
        if out_m and out_a:
            self.cbox_file_crtn_mode.addItem(modes["many_to_one"])  # Собрать все таблицы в один файл
        if inp_m and out_m:
            self.cbox_file_crtn_mode.addItem(modes["many_to_many"])  # Аналогично исходному
        # Если элементов combox не больше одного, выключение combox (Для индикации пользователю).
        if self.cbox_file_crtn_mode.count() == 1:
            self.cbox_file_crtn_mode.setEnabled(False)
        elif self.cbox_file_crtn_mode.count() > 1:
            self.cbox_file_crtn_mode.setEnabled(True)

    def initialize_cs(self) -> None:
        # Синхронизация СК
        out_geo_ref = self.wzrd.drivers[self.wzrd.out_gdal_format].get("geo_ref", None)
        out_cs_support = self.wzrd.drivers[self.wzrd.out_gdal_format].get("coord_sys_support", True)
        btn = self.findChild(QPushButton, "btn_choose_out_coord")
        if out_geo_ref and out_cs_support:
            if self.wzrd.inp_cs:
                self.wzrd.out_cs = self.wzrd.inp_cs
            else:
                self.wzrd.out_cs = self.wzrd.default_cs
            self.line_edit.setText(self.wzrd.out_cs.name)
            btn.setEnabled(True)
        else:
            self.line_edit.setText(tr("Выходной формат не поддерживает систему координат."))
            btn.setEnabled(False)

    def initialize_out_ext(self) -> None:
        # Обработка исключения для cbox выбора расширения, для выходного формата "MapInfo File"
        widget = self.widget_out_ext
        if self.wzrd.out_gdal_format == "MapInfo File":
            widget.hide()
        else:
            widget.show()
        # Инициализация cbox с расширениями
        self.cbox_out_ext.clear()
        # Поиск расширения в json по драйверу
        out_ext = self.wzrd.drivers[self.wzrd.out_gdal_format]["out_ext"]
        out_ext = helper.ensure_list(out_ext)
        self.cbox_out_ext.addItems(out_ext)

    def cleanupPage(self) -> None:
        """
        Вызывается при нажатии кнопки назад. Удаляет добавленную форму.
        """
        if self.form_view is not None:
            self.form_view.setParent(None)

    def validatePage(self) -> True:
        """
        Валидация страницы при нажатии кнопки далее. Считывает польз. ввод и выходное расширение.
        """
        # Считывание режима конвертации
        cbox_txt = self.cbox_file_crtn_mode.currentText()
        self.wzrd.file_creation_mode = helper.get_key(self.wzrd.file_creation_modes, cbox_txt)
        if self.wzrd.file_creation_mode == "many_to_one":
            lnd_txt = self.lnd_append_file_name.text()
            if lnd_txt:
                self.wzrd.file_name_for_append = lnd_txt

        # Считывание польз. ввода с формы после нажатия кнопки Далее
        form = self.form_view.widget()
        _, self.wzrd.dataset, self.wzrd.layer = jsonloader.read_form(form, self.wzrd.out_gdal_format,
                                                                     self.wzrd)
        # Считывание расширения
        self.wzrd.out_ext = self.cbox_out_ext.currentText()

        return True

    def eventFilter(self, watched, event) -> False:
        """
        Фильтр ставиться на поле ввода "Охват" для вых. формата MapInfo File во время расчета охвата.
        Нужен для отслеживания клика мыши по полю для отмены активного расчета охвата.
        """
        if event.type() == QEvent.MouseButtonRelease:
            self.lnd_clicked.emit()
        return False

    # Slots

    @Slot(str)
    def cbox_show_file_name_field(self, txt) -> None:
        """
        Показ или скрытие поля выбора имени для сборки в один файл.
        """
        self.wzrd.file_creation_mode = helper.get_key(self.wzrd.file_creation_modes, txt)
        if self.wzrd.file_creation_mode == "many_to_one":
            self.file_name_for_append.setEnabled(True)
        else:
            self.file_name_for_append.setEnabled(False)
            self.lnd_append_file_name.clear()
            self.wzrd.file_name_for_append = None

    @Slot()
    def choose_out_coord(self) -> None:
        """
        Выбор выходной СК при нажатии кнопки "...". Также вызывает функцию валидации СК.
        """
        # Обработка декартовых СК
        if self.wzrd.out_cs.non_earth:
            cs = CoordSystem.from_wkt(self.wzrd.out_cs.wkt)
        else:
            cs = CoordSystem.from_proj(self.wzrd.out_cs.proj)

        # Диалог выбора СК
        dlg = ChooseCoordSystemDialog(cs)
        if dlg.exec_() == QDialog.Accepted:
            cs = dlg.chosenCoordSystem()
            self.wzrd.out_cs = cs
            # Валидация СК
            self.validate_check_box()
            if self.wzrd.out_gdal_format == "MapInfo File":
                self.validate_bounds()
            self.line_edit.setText(cs.name)

    @Slot(int)
    def chbox_reproj_state_changed(self, state: int) -> None:
        """
        Галочка перепроецирования СК.
        """
        self.wzrd.reproject = helper.chkbox_state_dict.get(state, None)
        self.validate_bounds()

    @Slot(int)
    def check_box_options_state_changed(self, state: int) -> None:
        """
        tmp
        """
        if helper.chkbox_state_dict.get(state, None):
            self.setButtonText(QWizard.NextButton, self.wzrd.buttonText(QWizard.NextButton))
        else:
            self.setButtonText(QWizard.NextButton, tr("Конвертировать"))

    # Functions

    def validate_check_box(self) -> None:
        """
        Функция для проверки доступности галочки "Перепроецировать".
        """
        if self.wzrd.inp_cs and self.wzrd.out_cs:
            cs_is_equal = self.wzrd.inp_cs == self.wzrd.out_cs
            non_earth = self.wzrd.inp_cs.non_earth or self.wzrd.out_cs.non_earth
        else:
            cs_is_equal = True
            non_earth = False

        # Доступность галочки "Перепроецировать"
        if cs_is_equal or non_earth:
            # Откл. перепроецирование
            self.chbox_reproj.setEnabled(False)
            self.chbox_reproj.setChecked(False)
            self.label.setStyleSheet("QLabel {color:grey}")
        else:
            # Вкл. перепроецирование
            self.chbox_reproj.setEnabled(True)
            self.chbox_reproj.setChecked(True)
            self.label.setStyleSheet("QLabel {color:black}")

    def validate_bounds(self) -> None:
        """
        Функция выполняется при изменении выходной СК. Отвечает за изменение доступности параметра перепроецирования СК.
        Также проверяет необходимость расчета охвата для формата "MapInfo File".
        Вызывается при инициализации страницы и при выборе выходной ск.
        """

        if self.wzrd.out_gdal_format != "MapInfo File":
            return None

        if self.wzrd.inp_cs and self.wzrd.out_cs:
            cs_is_equal = self.wzrd.inp_cs == self.wzrd.out_cs  # Если входная ск = выходной ск
            out_non_earth = self.wzrd.out_cs.non_earth
        else:
            # Когда формат на входе/выходе не поддерживает СК
            cs_is_equal = True
            out_non_earth = False

        rect = self.wzrd.out_cs.rect

        # план-схема
        if out_non_earth:
            if cs_is_equal and rect:
                rect_str = "{},{},{},{}".format(rect.xmin, rect.ymin, rect.xmax, rect.ymax)
                self.line_edit_bounds.setText(rect_str)
            elif not cs_is_equal or not rect:
                self.calc_bounds_start_task()
        # долгота/широта
        else:
            if rect:
                rect_str = "{},{},{},{}".format(rect.xmin, rect.ymin, rect.xmax, rect.ymax)
                self.line_edit_bounds.setText(rect_str)
            else:
                self.calc_bounds_start_task()

    # Thread

    def calc_bounds_start_task(self) -> None:
        """
        Расчет охвата для MapInfo File через GetExtent() в отдельном потоке.
        """
        if self.task:
            return None

        def show_cancel_dlg() -> None:
            """
            Показывает диалог отмены охвата.
            """
            self.m_box.exec_()
            if self.m_box.result() == QMessageBox.Yes:
                if self.task:
                    self.task.progress_handler().cancel()

        # Блок гуи
        self.btn_widget.setEnabled(False)

        self.findChild(QPushButton, "btn_choose_out_coord").setEnabled(False)

        self.wzrd.starting_task()

        # Изменение режима поля на "расчет охвата"
        self.line_edit_bounds.setReadOnly(True)
        self.lnd_bounds_last_text = self.line_edit_bounds.text()
        self.line_edit_bounds.setValidator(None)
        self.line_edit_bounds.setText(tr("Вычисление..."))
        self.line_edit_bounds.installEventFilter(self)
        self.lnd_clicked.connect(show_cancel_dlg)

        # Запуск функции расчета охвата в отдельном потоке
        self.task = AxipyAnyCallableTask(self.calculate_map_info_file_bounds)
        self.task.with_handler(False)
        self.task.progress_handler().finished.connect(self.finished)
        task_manager.start_task(self.task)
        # end

    def calculate_map_info_file_bounds(self) -> Optional[str]:
        """
        Расчет охвата.

        :return: x_min,y_min,x_max,y_max
        """
        try:
            # Сбор охвата каждого входного файла в список
            extents_list = list()
            path_list = [str(path) for path in self.wzrd.input_path_list]
            for path in path_list:
                if self.task.progress_handler().is_canceled():
                    return None

                try:
                    ds = gdal.OpenEx(path)
                    layer = ds.GetLayer()
                    extent = layer.GetExtent()
                except Exception as e:
                    raise e
                finally:
                    ds = None

                x_min, x_max, y_min, y_max = extent
                if x_min > x_max or y_min > y_max:
                    raise Exception("Минимальное значение больше максимального")

                extents_list.append(extent)

            # Определение минимального и максимального x и y
            x_min = min([extent[0] for extent in extents_list])
            x_max = max([extent[1] for extent in extents_list])
            y_min = min([extent[2] for extent in extents_list])
            y_max = max([extent[3] for extent in extents_list])

            # Расчет ширины и высоты
            width = (x_max - x_min)
            height = (y_max - y_min)

            if self.wzrd.reproject:
                # Трансформация точек с учетом СК
                ct = CoordTransformer(self.wzrd.inp_cs, self.wzrd.out_cs)
                dx = width / 100
                dy = height / 100
                pnts = ct.transform(
                    [Pnt(x_min + i * dx, y_min + j * dy) for i in range(0, 101) for j in range(0, 101)]
                )
                x_min = min(pnt.x for pnt in pnts)
                y_min = min(pnt.y for pnt in pnts)
                x_max = max(pnt.x for pnt in pnts)
                y_max = max(pnt.y for pnt in pnts)
                width = (x_max - x_min)
                height = (y_max - y_min)

            max_length = max(width, height)
            if max_length == 0:
                max_length = max(abs(x_max), abs(y_max))
                if max_length == 0:
                    max_length = 1e8

            p = ceil(log10(abs(max_length)))
            if abs(p) >= 20:
                raise Exception("Слишком большое значение охвата")

            def in_extent(extent1: int, extent2: int) -> bool:
                """
                Функция проверяет нахождение точек во входном промежутке, по x и по y.
                """
                if (
                        extent1 < x_min and x_max < extent2 and
                        extent1 < y_min and y_max < extent2):
                    return True
                else:
                    return False

            if in_extent(-10 ** p, 10 ** p):
                x_min, y_min, x_max, y_max = (-10 ** p, -10 ** p, 10 ** p, 10 ** p)
            else:
                if in_extent(0, 2 * 10 ** p):
                    x_min, y_min, x_max, y_max = (0, 0, 2 * 10 ** p, 2 * 10 ** p)
                else:
                    if in_extent(-2 * 10 ** p, 0):
                        x_min, y_min, x_max, y_max = (-2 * 10 ** p, -2 * 10 ** p, 0, 0)
                    else:
                        def circle_center(arg_1: float, arg_2: float) -> int:
                            """
                            Функция поиска круглой середины.
                            """
                            return round((arg_1 + arg_2) / 2 / 10 ** p) * 10 ** p

                        c_x = circle_center(x_min, x_max)
                        c_y = circle_center(y_min, y_max)

                        x_min, y_min, x_max, y_max = (c_x - 10 ** p, c_y - 10 ** p, c_x + 10 ** p, c_y + 10 ** p)
        except (Exception,):
            if debug:
                traceback.print_exc()
            helper.notify(tr("Не получилось рассчитать охват"))
        else:
            return f"{x_min},{y_min},{x_max},{y_max}"

    @Slot()
    def finished(self) -> None:
        """
        Вызывается при завершении расчета охвата в потоке. Отменяет режим расчета охвата (блокировка гуи и т.д.).
        """
        if self.task.progress_handler().is_canceled():
            helper.notify(tr("Расчет охвата отменен."), 0)

        # Ввод результата расчета в поле ввода
        txt = self.task.progress_handler().result
        if txt:
            self.line_edit_bounds.setText(txt)
        else:
            self.line_edit_bounds.setText(self.lnd_bounds_last_text)

        self.line_edit_bounds.removeEventFilter(self)
        self.lnd_clicked.disconnect()
        # Возврат валидатора ввода охвата
        self.line_edit_bounds.setValidator(
            QRegExpValidator(QRegExp(r"-?\d+\.?\d+,-?\d+\.?\d+,-?\d+\.?\d+,-?\d+\.?\d+")))
        self.line_edit_bounds.setReadOnly(False)
        # Включение элементов, выключенных во время блока гуи
        self.btn_widget.setEnabled(True)
        self.findChild(QPushButton, "btn_choose_out_coord").setEnabled(True)

        layout = [QWizard.Stretch, QWizard.BackButton, QWizard.NextButton,
                  QWizard.CancelButton]
        self.wzrd.setButtonLayout(layout)

        self.wzrd.finishing_task()
