import enum
import os
from typing import Optional, List
from dataclasses import dataclass
import logging

from PySide2.QtCore import Qt
from PySide2.QtGui import QIcon
from PySide2.QtUiTools import QUiLoader
from PySide2.QtWidgets import QComboBox, QDialog, QFileDialog

from axipy import (view_manager, data_manager, MapView, CurrentSettings, ChooseCoordSystemDialog, Version,
                    BoundingRectDialog, Plugin, mainwindow, CoordSystem)

from .CoordinateProcess import TypeCoordinatesData, CoordinateProcess


class TypeResultAction(enum.Enum):
    current_layer = 0
    new_table = 1


@dataclass
class LoadPointsLastUsedData:
    typeCoordinates: TypeCoordinatesData = TypeCoordinatesData.value
    is_closed: bool = True
    field_geom: str = None
    field_geom_part: str = None
    field_geom_point: str = None
    field_geom_value1: str = None
    field_geom_value2: str = None


class LoadPointsDialog(QDialog):
    NoneValue: str = ''
    _ResultAction: TypeResultAction = TypeResultAction.current_layer

    def __init__(self, iface: Plugin, coordsystem: CoordSystem, lastUsed: LoadPointsLastUsedData) -> None:
        if Version.compare(6,2) == -1:
            super().__init__(mainwindow.widget)
        else:
            super().__init__(mainwindow.qt_object())
        self._tr = iface.tr
        self._last_used = lastUsed
        self._coordsystem = coordsystem
        ui_file = os.path.join(os.path.dirname(__file__), "LoadPointsDialog.ui")
        self._ui = QUiLoader().load(ui_file, self)
        self._load_ui()
        self._ui.setParent(view_manager.global_parent)
        self._update_button_text(coordsystem)
        self._ui.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
        self._out_filename = ''

        self._ui.cbTypeData.setCurrentIndex(lastUsed.typeCoordinates)
        self._ui.cbClosedAsPolygon.setCheckState(Qt.Checked if lastUsed.is_closed else Qt.Unchecked)

    def _load_ui(self) -> None:
        for t in data_manager.tables:
            if len(t.schema) >= 2:
                self._ui.cbTables.addItem(t.name)
        self._ui.cbTables.currentIndexChanged.connect(self._tables_index_changed)
        self._ui.cbTypeData.setCurrentIndex(-1)
        self._ui.cbTypeData.currentIndexChanged.connect(self._type_data_index_changed)
        self._ui.pushButtonSK.clicked.connect(self._choose_coord_system)
        if self._ui.cbTables.count():
            self._tables_index_changed(0)
        self._ui.pbExport.clicked.connect(self._export)
        self._ui.pbExport.setEnabled(self._ui.cbTables.count())
        self._ui.pbInsert.clicked.connect(self._insert)
        can_insert = isinstance(view_manager.active, MapView) and view_manager.active.map.editable_layer is not None
        self._ResultAction = TypeResultAction.current_layer if can_insert else TypeResultAction.new_table
        self._ui.pbInsert.setEnabled(can_insert and self._ui.cbTables.count())
        self._ui.pbClose.clicked.connect(self._close)
        self._ui.setWindowIcon(QIcon(os.path.join(os.path.dirname(__file__), 'open.png')))

    def _accept_close(self) -> None:
        self._ui.close()
        self._ui.setResult(QDialog.Accepted)

    def last_used_values(self) -> Optional[str]:
        def f_fn(fn):
            return fn if fn != '' else None

        res = LoadPointsLastUsedData()
        res.typeCoordinates = self.result_type_coordinates()
        res.is_closed = self.result_is_closed_polygon()
        res.field_geom = f_fn(self._ui.cbGeometryId.currentText())
        res.field_geom_part = f_fn(self._ui.cbGeometryPartId.currentText())
        res.field_geom_point = f_fn(self._ui.cbPointNumber.currentText())
        res.field_geom_value1 = f_fn(self._ui.cbValue1.currentText())
        res.field_geom_value2 = f_fn(self._ui.cbValue2.currentText())
        return res

    def _export(self) -> None:
        dialog = QFileDialog(
            parent=self,
            filter="MapInfo TAB (*.tab)",
            directory=str(CurrentSettings.LastSavePath),
            # caption=self._tr('Имя выходного файла'),
        )
        dialog.setAcceptMode(QFileDialog.AcceptSave)
        dialog.setDefaultSuffix("tab")
        if dialog.exec():
            fn = dialog.selectedFiles()[0]
            CurrentSettings.LastSavePath=os.path.dirname(fn)
            self._out_filename = fn
            self._ResultAction = TypeResultAction.new_table
            self._accept_close()

    def _insert(self) -> None:
        self._ResultAction = TypeResultAction.current_layer
        self._accept_close()

    def _close(self) -> None:
        self._ui.close()
        self._ui.setResult(QDialog.Rejected)

    def _update_button_text(self, coordsystem: CoordSystem) -> None:
        if coordsystem is not None:
            self._ui.pushButtonSK.setText(coordsystem.title)

    def _choose_coord_system(self) -> None:
        dialog = ChooseCoordSystemDialog(self._coordsystem)
        if dialog.exec() == QDialog.Accepted:
            self._coordsystem = dialog.chosenCoordSystem()
            if Version.segments()[0] >= 5:
                if self._coordsystem.non_earth:
                    dlg = BoundingRectDialog()
                    dlg.rect = self._coordsystem.rect
                    dlg.unit = self._coordsystem.unit.name
                    if dlg.exec() == QDialog.Accepted:
                        self._coordsystem.rect = dlg.rect
            self._update_button_text(self._coordsystem)

    def _type_data_index_changed(self, idx: int) -> None:
        if idx == 0:
            self._ui.lblValue1.setText(self._tr('Х координата:'))
            self._ui.lblValue2.setText(self._tr('Y координата:'))
        else:
            self._ui.lblValue1.setText(self._tr('Расстояние (м.):'))
            self._ui.lblValue2.setText(self._tr('Азимут (град.):'))

    @staticmethod
    def _set_items(cb: QComboBox, attrs: List[str], attrs_valid: List[str]) -> None:
        cb.clear()
        cb.addItems([i for i in attrs if i is not None])
        for a in (a for a in attrs_valid if a is not None):
            idx = cb.findText(a, Qt.MatchFixedString)
            if idx != -1:
                cb.setCurrentIndex(idx)
                return
        cb.setCurrentIndex(-1)

    def _tables_index_changed(self, idx: int) -> None:
        t = self._result_table()
        if t is not None:
            attrs = t.schema.attribute_names
            self._set_items(self._ui.cbGeometryId, attrs,
                            [self._last_used.field_geom, CoordinateProcess.IdFeatureField])
            self._ui.cbGeometryId.insertItem(0, self.NoneValue)
            self._set_items(self._ui.cbGeometryPartId, attrs,
                            [self._last_used.field_geom_part, CoordinateProcess.IdGeometryField])
            self._ui.cbGeometryPartId.insertItem(0, self.NoneValue)
            self._set_items(self._ui.cbPointNumber, attrs,
                            [self._last_used.field_geom_point, CoordinateProcess.IdPointField])
            self._ui.cbPointNumber.insertItem(0, self.NoneValue)
            self._set_items(self._ui.cbValue1, attrs,
                            [self._last_used.field_geom_value1] + CoordinateProcess.ValueFirstFieldList)
            self._set_items(self._ui.cbValue2, attrs,
                            [self._last_used.field_geom_value2] + CoordinateProcess.ValueSecondFieldList)

    def result_record_count(self) -> int:
        return self._result_table().count()

    def result_sql_text(self) -> str:
        # Результирующий SQL запрос
        def _ct(cb):
            return cb.currentText()
        def _add(s, v):
            if s == '':
                return v
            return s + f', {v}'
        geom_id = _ct(self._ui.cbGeometryId) 
        geom_part_id = _ct(self._ui.cbGeometryPartId)
        is_ordered = geom_id != self.NoneValue
        if is_ordered:
            sel = f'{geom_id}'
            order = f'{geom_id}'
        else:
            sel = ''
        if is_ordered and geom_part_id != self.NoneValue:
            sel = _add(sel, geom_part_id)
            order += f', abs(int({geom_part_id}))'
        p_num_name = _ct(self._ui.cbPointNumber)
        if p_num_name != self.NoneValue:
            t = self._result_table()
            need_convert = False
            if t is not None:
                a = t.schema.by_name(p_num_name)
                if a:
                    need_convert = a.typedef.split(':', 1)[0] in ['string']
            sel = _add(sel, p_num_name)
            if is_ordered:
                order += f', int({p_num_name})' if need_convert else f', {p_num_name}'
        sel = _add(sel, _ct(self._ui.cbValue1))
        sel = _add(sel, _ct(self._ui.cbValue2))
        query_text = f'select {sel} from {self._result_table().name}'
        if is_ordered:
            query_text += f' order by {order}'
        logging.debug(f'sql: {query_text}')
        return query_text

    def result_coordsystem(self) -> CoordSystem:
        return self._coordsystem

    def _result_table(self) -> str:
        # Исходная таблица
        return data_manager.find(self._ui.cbTables.currentText())

    def result_type_coordinates(self) -> TypeCoordinatesData:
        return TypeCoordinatesData(self._ui.cbTypeData.currentIndex())

    def result_is_closed_polygon(self) -> bool:
        return self._ui.cbClosedAsPolygon.checkState() == Qt.Checked
    
    def result_is_closed_linear(self) -> bool:
        return self._ui.cbClosedLinearDuplicates.checkState() == Qt.Checked

    def result_type_action(self) -> TypeResultAction:
        return self._ResultAction

    def result_filename(self) -> str:
        return self._out_filename

    def result_has_number(self) -> bool:
        return self._ui.cbGeometryId.currentText() != self.NoneValue
    
    def result_has_part_number(self) -> bool:
        return self._ui.cbGeometryPartId.currentText() != self.NoneValue

    def result_has_point_number(self) -> bool:
        return self._ui.cbPointNumber.currentText() != self.NoneValue

    def exec(self) -> QDialog.DialogCode:
        return self._ui.exec()
