#!/usr/bin/python3
import os
import subprocess
import sys

from PyQt5.QtCore import QThread, Qt
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QApplication, QWidget, QGridLayout, QMainWindow, QPushButton, QCheckBox, QMessageBox, \
    QVBoxLayout, QLabel, QSlider, QGroupBox, QMenu, QAction, QSpinBox

from xinputcalibrator_classes import ThreadWorker, help_text, config_address


class MyWindow(QMainWindow):

    def alert(self, message, title='Внимание!'):
        dlg = QMessageBox()
        dlg.setWindowTitle(title)
        dlg.setText(message)
        dlg.exec()

    def choice_question(self, message, title='Внимание!'):
        dlg = QMessageBox()
        dlg.setWindowTitle(title)
        dlg.setText(message)
        dlg.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
        return dlg.exec() == QMessageBox.Ok

    def remove_driver(self):
        self.statusBar().showMessage('Подождите...')
        self.wait_alert_box = QMessageBox()
        self.wait_alert_box.setWindowTitle('Внимание!')
        self.wait_alert_box.setText('Подождите, идёт удаление драйвера...')
        self.wait_alert_box.setStandardButtons(QMessageBox.Ok)
        self.wait_alert_box.buttons()[0].setEnabled(False)
        self.thread = QThread()
        self.worker = ThreadWorker()
        self.worker.command = 'pkexec dnf remove -y x11-driver-input-libinput'
        self.worker.moveToThread(self.thread)
        self.thread.started.connect(self.worker.run)
        self.worker.start_signal.connect(lambda: self.wait_alert_box.exec())
        self.worker.finished.connect(self.worker.deleteLater)
        self.worker.finished.connect(self.thread.quit)
        self.worker.finished.connect(lambda: self.statusBar().clearMessage())
        self.thread.finished.connect(self.thread.deleteLater)
        self.thread.finished.connect(lambda: self.wait_alert_box.close())
        self.worker.error_signal.connect(lambda message: self.alert(f'Возникла ошибка: {message}'))
        self.worker.success_signal.connect(self.reboot_after_removing_driver)
        self.thread.start()

    def reboot_after_removing_driver(self):
        if self.choice_question('Драйвер удалён успешно. Для продолжения калибровки необходимо перезапустить компьютер. '
                                'Выполнить перезагрузку сейчас?'):
            subprocess.run('reboot', shell=True)
        self.remove_driver_button.setEnabled(False)

    def run_calibrator(self):
        if self.sensitivity_checkbox.isChecked() and self.finger_high_slider.value() <= self.finger_low_slider.value():
            self.alert('Значение "FingerHigh" должно быть больше, чем "FingerLow"')
            return
        misclick_addition = '' if not self.misclick_disable_required else ' --misclick 0'
        run_command = subprocess.run(f'xinput_calibrator --output-type xorg.conf.d{misclick_addition} 2>&1',
                                     shell=True, capture_output=True)
        if run_command.returncode != 0:
            if subprocess.run('rpm -q xinput_calibrator', shell=True).returncode != 0:
                self.alert('Установите xinput_calibrator и перезапустите программу.')
                sys.exit(1)
            else:
                if self.choice_question('На данном устройстве не найден сенсорный экран. '
                                        'Запустить калибровку в режиме тестирования?'):
                    run_command = subprocess.run(f'xinput_calibrator --fake --output-type '
                                                 f'xorg.conf.d{misclick_addition} 2>&1', shell=True, capture_output=True)
                    if run_command.returncode != 0:
                        self.alert('Калибровка завершилась неудачно.')
                        return
                else:
                    return

        res = run_command.stdout.decode()
        if 'Section "InputClass"' in res:
            self.calibration_output = res
            self.save_res_button.setEnabled(True)
            self.save_res_button.setToolTip('')
            if self.choice_question(f'Калибровка завершилась успешно. Сохранить результат в {config_address}?'):
                self.save_result()
        else:
            self.alert(f'Калибровка завершилась неудачно.\nСообщение калибратора: {res}')

    def misclick_checkbox_changed(self):
        self.misclick_disable_required = self.misclick_checkbox.isChecked()

    def sensitivity_checkbox_changed(self):
        self.sensitivity_groupbox.setEnabled(self.sensitivity_checkbox.isChecked())
        if not self.sensitivity_checkbox.isChecked():
            self.statusBar().clearMessage()
        else:
            self.update_sliders_values()

    def save_result(self):
        if self.sensitivity_checkbox.isChecked() and self.finger_high_slider.value() <= self.finger_low_slider.value():
            self.alert('Значение "FingerHigh" должно быть больше, чем "FingerLow"')
            return
        if not self.calibration_output:
            self.calibration_output = [line.rstrip() for line in open(config_address, 'r').readlines()]
        else:
            self.calibration_output = self.calibration_output.split('\n')
        start, end, fingerlow_index, fingerhigh_index = -1, -1, -1, -1
        for i in range(len(self.calibration_output)):
            if 'Section "InputClass"' in self.calibration_output[i]:
                start = i
            if 'EndSection' in self.calibration_output[i]:
                end = i
        if start != -1 and end != -1:
            self.calibration_output = self.calibration_output[start:end + 1]
            for i in range(len(self.calibration_output)):
                if 'Option "FingerLow"' in self.calibration_output[i]:
                    fingerlow_index = i
                if 'Option "FingerHigh"' in self.calibration_output[i]:
                    fingerhigh_index = i
            if self.sensitivity_checkbox.isChecked():
                if fingerlow_index == -1:
                    self.calibration_output.insert(
                        len(self.calibration_output) - 1, f'\tOption "FingerLow" "{self.finger_low_slider.value()}"')
                else:
                    self.calibration_output[
                        fingerlow_index] = f'\tOption "FingerLow" "{self.finger_low_slider.value()}"'
                if fingerhigh_index == -1:
                    self.calibration_output.insert(
                        len(self.calibration_output) - 1, f'\tOption "FingerHigh" "{self.finger_high_slider.value()}"')
                else:
                    self.calibration_output[
                        fingerhigh_index] = f'\tOption "FingerHigh" "{self.finger_high_slider.value()}"'
                self.remove_sensivity_button.setEnabled(True)
            else:
                if fingerlow_index != -1:
                    fingerlow_line = self.calibration_output[fingerlow_index]
                if fingerhigh_index != -1:
                    fingerhigh_line = self.calibration_output[fingerhigh_index]
                if fingerlow_index != -1:
                    self.calibration_output.remove(fingerlow_line)
                if fingerhigh_index != -1:
                    self.calibration_output.remove(fingerhigh_line)
                self.remove_sensivity_button.setEnabled(False)
            while self.calibration_output[-1].strip() == '':
                self.calibration_output.pop()
            self.calibration_output = '\n'.join(self.calibration_output)
            temp_file = subprocess.run('mktemp', shell=True, capture_output=True).stdout.decode().strip()
            print(self.calibration_output, file=open(temp_file, 'w'))

            run_command = subprocess.run(f'pkexec bash -c "cat {temp_file}>{config_address}"',
                                         shell=True, capture_output=True)
            if run_command.returncode == 0:
                self.alert('Настройки успешно сохранены.')
            else:
                self.alert(f'Не удалось сохранить настройки.\nСообщение об ошибке: {run_command.stdout.decode()}')
            os.remove(temp_file)
            self.delete_config_file_button.setEnabled(True)

    def update_sliders_values(self):
        self.low_finger_value_spinbox.setValue(self.finger_low_slider.value())
        self.high_finger_value_spinbox.setValue(self.finger_high_slider.value())
        if self.finger_low_slider.value() > 60 or self.finger_high_slider.value() > 60:
            self.statusBar().showMessage('Не рекомендуются значения чувствительности больше 60.')
        else:
            self.statusBar().clearMessage()

    def update_spinbox_value(self):
        for spinbox in (self.low_finger_value_spinbox, self.high_finger_value_spinbox):
            if spinbox.value() < 0:
                spinbox.setValue(0)
            if spinbox.value() > 255:
                spinbox.setValue(255)
        self.finger_low_slider.setValue(self.low_finger_value_spinbox.value())
        self.finger_high_slider.setValue(self.high_finger_value_spinbox.value())

    def detect_is_sensivity_exists(self):
        try:
            with open(config_address, 'r') as inp:
                lines = inp.readlines()
            section, fingerlow, fingerhigh = False, False, False
            for line in lines:
                if 'Section "InputClass"' in line:
                    section = True
                if 'EndSection' in line:
                    section = False
                if section:
                    if 'Option "FingerLow"' in line:
                        fingerlow = True
                        fingerlow_value = int(''.join([i for i in line if i.isdigit()]))
                    if 'Option "FingerHigh"' in line:
                        fingerhigh = True
                        fingerhigh_value = int(''.join([i for i in line if i.isdigit()]))
        except Exception:
            return False
        if fingerhigh and fingerlow:
            self.finger_low_slider.setValue(fingerlow_value)
            self.finger_high_slider.setValue(fingerhigh_value)
            self.sensitivity_checkbox.setChecked(True)
        return fingerhigh and fingerlow

    def remove_sensivity(self):
        try:
            with open(config_address, 'r') as inp:
                lines = inp.readlines()
                new_lines = []
            section, fingerlow, fingerhigh = False, False, False
            for line in lines:
                if 'Section "InputClass"' in line:
                    section = True
                if 'EndSection' in line:
                    section = False
                if section:
                    if 'Option "FingerLow"' in line or 'Option "FingerHigh"' in line:
                        continue
                new_lines.append(line)
        except Exception:
            return
        temp_file = subprocess.run('mktemp', shell=True, capture_output=True).stdout.decode().strip()
        with open(temp_file, 'w') as out:
            print(*new_lines, file=out)
        run_command = subprocess.run(f'pkexec bash -c "cat {temp_file}>{config_address}"',
                                     shell=True, capture_output=True)
        if run_command.returncode == 0:
            self.alert('Настройки успешно сохранены.')
        else:
            self.alert(f'Не удалось сохранить настройки.\nСообщение об ошибке: {run_command.stdout.decode()}')
        os.remove(temp_file)
        self.remove_sensivity_button.setEnabled(False)

    def show_help(self):
        self.alert(help_text, title='Справка')

    def open_config_file_in_kate(self):
        command_code = subprocess.run(f'kate {config_address}', shell=True).returncode
        if command_code != 0:
            self.alert('Не удалось открыть файл конфига в текстовом редакторе.')

    def delete_config_file(self):
        res = subprocess.run(f'pkexec rm -rf {config_address}', shell=True).returncode
        if res:
            self.alert('Не удалось удалить файл конфига.')
        else:
            self.alert('Файл конфига успешно удалён.')
            self.delete_config_file_button.setEnabled(False)
            self.save_res_button.setEnabled(False)
            self.remove_sensivity_button.setEnabled(False)

    def __init__(self):
        QMainWindow.__init__(self)
        self.misclick_disable_required = False
        self.calibration_output = ''
        self.initUI()

    def initUI(self):
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        self.statusBar().showMessage('')
        layout = QVBoxLayout()
        central_widget.setLayout(layout)

        self.remove_driver_button = QPushButton('Удалить x11-driver-input-libinput')
        self.remove_driver_button.clicked.connect(self.remove_driver)
        if subprocess.run('rpm -q x11-driver-input-libinput', shell=True).returncode != 0:
            self.remove_driver_button.setDisabled(True)
            self.remove_driver_button.setToolTip('x11-driver-input-libinput уже удалён')
        layout.addWidget(self.remove_driver_button)

        calibrate_button = QPushButton('Запустить калибровку')
        calibrate_button.clicked.connect(self.run_calibrator)
        layout.addWidget(calibrate_button)

        self.misclick_checkbox = QCheckBox('Отключить misclick')
        self.misclick_checkbox.stateChanged.connect(self.misclick_checkbox_changed)
        layout.addWidget(self.misclick_checkbox)

        self.sensitivity_checkbox = QCheckBox('Добавить регулировку чувствительности')
        self.sensitivity_checkbox.stateChanged.connect(self.sensitivity_checkbox_changed)
        layout.addWidget(self.sensitivity_checkbox)

        self.sensitivity_groupbox = QGroupBox('Регулировка чувствительности')
        sensitivity_groupbox_layout = QGridLayout()
        self.sensitivity_groupbox.setLayout(sensitivity_groupbox_layout)
        sensitivity_groupbox_layout.addWidget(QLabel('FingerLow'), 0, 0)
        self.finger_low_slider = QSlider()
        self.finger_low_slider.setRange(0, 255)
        self.finger_low_slider.setOrientation(Qt.Horizontal)
        self.finger_low_slider.valueChanged.connect(self.update_sliders_values)
        sensitivity_groupbox_layout.addWidget(self.finger_low_slider, 0, 1)
        self.low_finger_value_spinbox = QSpinBox()
        self.low_finger_value_spinbox.setFixedWidth(50)
        self.low_finger_value_spinbox.setRange(0, 255)
        self.low_finger_value_spinbox.valueChanged.connect(self.update_spinbox_value)
        sensitivity_groupbox_layout.addWidget(self.low_finger_value_spinbox, 0, 2)
        sensitivity_groupbox_layout.addWidget(QLabel('FingerHigh'), 1, 0)
        self.finger_high_slider = QSlider()
        self.finger_high_slider.setRange(0, 255)
        self.finger_high_slider.setOrientation(Qt.Horizontal)
        self.finger_high_slider.valueChanged.connect(self.update_sliders_values)
        sensitivity_groupbox_layout.addWidget(self.finger_high_slider, 1, 1)
        self.high_finger_value_spinbox = QSpinBox()
        self.high_finger_value_spinbox.setFixedWidth(50)
        self.high_finger_value_spinbox.setRange(0, 255)
        self.high_finger_value_spinbox.valueChanged.connect(self.update_spinbox_value)
        self.finger_low_slider.setValue(15)
        self.finger_high_slider.setValue(20)

        sensitivity_groupbox_layout.addWidget(self.high_finger_value_spinbox, 1, 2)

        self.update_sliders_values()
        self.sensitivity_checkbox_changed()

        layout.addWidget(self.sensitivity_groupbox)

        self.remove_sensivity_button = QPushButton('Удалить настройки чувствительности')
        self.remove_sensivity_button.clicked.connect(self.remove_sensivity)
        if not self.detect_is_sensivity_exists():
            self.remove_sensivity_button.setDisabled(True)
        layout.addWidget(self.remove_sensivity_button)

        self.save_res_button = QPushButton(f'Записать результат в {config_address}')
        if not os.path.isfile(config_address):
            self.save_res_button.setEnabled(False)
            self.save_res_button.setToolTip('Сначала необходимо выполнить калибровку')
        self.save_res_button.clicked.connect(self.save_result)
        layout.addWidget(self.save_res_button)

        open_config_file_in_kate_button = QPushButton('Открыть файл конфига в Kate')
        open_config_file_in_kate_button.clicked.connect(self.open_config_file_in_kate)
        layout.addWidget(open_config_file_in_kate_button)
        
        self.delete_config_file_button = QPushButton('Удалить файл конфига')
        self.delete_config_file_button.clicked.connect(self.delete_config_file)
        if not os.path.isfile(config_address):
            self.delete_config_file_button.setEnabled(False)
        layout.addWidget(self.delete_config_file_button)

        menuBar = self.menuBar()
        fileMenu = QMenu("Файл", self)
        menuBar.addMenu(fileMenu)
        help_action = QAction('Справка', fileMenu)
        help_action.triggered.connect(self.show_help)
        fileMenu.addAction(help_action)
        exit_action = QAction('Выход', fileMenu)
        exit_action.triggered.connect(lambda: self.close())
        fileMenu.addAction(exit_action)
        self.setGeometry(50, 50, 400, 100)
        self.setWindowTitle("Калибровка сенсорного экрана")
        self.setWindowIcon(QIcon(QIcon.fromTheme('xinput-calibrator-gui')))
        self.show()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MyWindow()
    sys.exit(app.exec_())
