#!/usr/bin/env python
# -*- coding: utf-8 -*-

import tkinter as tk
from tkinter import ttk
from tkinter import messagebox as mb, filedialog as fd
import webbrowser
from pathlib import Path

from . import __version__, __progname__
from .rc import status_tips
from .core import Core, CertificateNotSelected
from .routines import write_traceback


def word_wrap(s, width):
    """ Перенос строки по словам для умещения в поле шириной w"""
    lines = []
    for i, word in enumerate(s.split(' ')):
        if i == 0:
            lines.append(word)
            continue
        if len(lines[-1]) + len(word) + 4 <= width:
            lines[-1] += ' %s' % word
        else:
            lines.append(word)
    return '\n'.join(lines)


class MainWindow():
    def __init__(self, root, txtwidth=42):
        self.core = Core()
        self.root = root
        self.txtwidth = txtwidth
        self.icon = tk.PhotoImage(file='./res/sign.png')

        root.iconphoto(False, self.icon)
        root.title("%s v%s" % (__progname__, __version__))
        root.geometry('1150x560+150+204')

        # Настройка строки меню:
        menubar = tk.Menu(root, relief=tk.FLAT)

        self.filemenu = tk.Menu(menubar, tearoff=0)
        self.filemenu.add_command(label="Добавить файлы...",
                                  command=self._add_files)
        self.filemenu.add_separator()
        self.filemenu.add_command(label="Настройки",
                                  command=self._setup)
        self.filemenu.add_separator()
        self.filemenu.add_command(label="Выход", command=root.quit)

        self.editmenu = tk.Menu(menubar, tearoff=0)
        self.editmenu.add_command(label="Выделить все файлы",
                                  command=self._select_all_files)
        self.editmenu.add_command(label="Снять выделение",
                                  command=self._unselect_all_files)
        self.editmenu.add_command(label="Удалить выделенные",
                                  command=self._del_selected_files)
        self.editmenu.add_command(label="Очистить список",
                                  command=self._clear_list)

        self.certmenu = tk.Menu(menubar, tearoff=0)
        self.certmenu.add_command(label="Импортировать сертификат...",
                                  command=self._import_cert)
        self.certmenu.add_command(label="Удалить выбранный сертификат",
                                  command=self._del_cert)

        self.helpmenu = tk.Menu(menubar, tearoff=0)
        self.helpmenu.add_command(label="Помощь",
                                  command=self._show_help)
        self.helpmenu.add_command(label="О программе",
                                  command=self._show_about_window)

        menubar.add_cascade(label="Файл", menu=self.filemenu)
        menubar.add_cascade(label="Правка", menu=self.editmenu)
        menubar.add_cascade(label="Сертификат", menu=self.certmenu)
        menubar.add_cascade(label="Справка", menu=self.helpmenu)
        root.config(menu=menubar)

        # Верхний разделитель:
        upper_sep = ttk.Separator(root, orient='horizontal')
        upper_sep.grid(column=0, columnspan=2, row=0, sticky='WE',
                       padx=(3, 3), pady=(3, 3))

        # Настройка левого фрейма (таблица, кнопки):
        self.lframe = ttk.Frame(root, padding=(0, 0, 0, 0), relief=tk.FLAT)
        self.lframe.grid(column=0, row=1, sticky='NSWE')

        self.rframe = ttk.Frame(root, padding=(0, 0, 0, 0), relief=tk.FLAT)
        self.rframe.grid(column=1, row=1, sticky='NSWE')

        root.grid_columnconfigure(0, weight=1)
        root.grid_rowconfigure(1, weight=1)

        self.lframe.grid_columnconfigure(0, weight=1)
        self.lframe.grid_rowconfigure(0, weight=1)

        self.rframe.grid_rowconfigure(2, weight=1)

        self.tree = ttk.Treeview(self.lframe, columns=('Status', ))
        self.tree.grid(column=0, row=0, columnspan=16, rowspan=7,
                       sticky='NSWE')
        self.tree.bind("<<TreeviewSelect>>", self._item_select)
        self.tree.column('#0', width=280, anchor=tk.E)
        self.tree.column('Status', width=140, anchor='center')
        self.tree.heading('#0', text='Файл', anchor='center')
        self.tree.heading('Status', text='Статус', anchor='center')
        self.tree.tag_configure('NOT_SIGNED', foreground='#000000')
        self.tree.tag_configure('SIGNED', foreground='#00BB02')
        self.tree.tag_configure('JSON_MISSING', foreground='#FF2727')

        self.scrlbar = ttk.Scrollbar(self.lframe, orient=tk.VERTICAL,
                                     command=self.tree.yview)
        self.scrlbar.grid(column=16, rowspan=7, row=0, sticky='NSE')

        self.signbtn = ttk.Button(self.lframe, text='Подписать выбранные',
                                  command=self._sign_files, state='enabled',
                                  width=txtwidth)
        self.signbtn.grid(column=0, row=7, sticky='SW',
                          padx=(8, 8), pady=(8, 8))
        self.check_signbtn = ttk.Button(self.lframe, text='Проверить ЭЦП',
                                        command=self._check_signs,
                                        state='enabled')
        self.check_signbtn.grid(column=1, row=7, sticky='S',
                                padx=(8, 8), pady=(8, 8))
        self.unsignbtn = ttk.Button(self.lframe, text='Удалить ЭЦП',
                                    command=self._unsign_files,
                                    state='enabled')
        self.unsignbtn.grid(column=2, row=7, sticky='SE',
                            padx=(8, 8), pady=(8, 8))

        # Настройка правого фрейма (выбор сертификата и его свойства):
        self.cselectlbl = ttk.Label(self.rframe, text='Выбор сертификата:',
                                    anchor='nw')
        self.cselectlbl.grid(column=0, row=0, sticky='NWE',
                             padx=(8, 8), pady=(8, 8))

        self.cselect = ttk.Combobox(self.rframe)
        self.cselect.bind("<<ComboboxSelected>>", self._cert_select)
        self.cselect['values'] = self.core.cert_keys
        self.cselect.grid(column=0, row=1, sticky='NWE',
                          padx=(8, 8), pady=(8, 8))

        self.certinfotxt = tk.StringVar()

        self.certinfo = ttk.Label(self.rframe, textvariable=self.certinfotxt,
                                  anchor='nw', width=txtwidth)
        self.certinfo.grid(column=0, row=2, rowspan=6, sticky='NWE',
                           padx=(8, 8), pady=(8, 8))
        self._reset_rframe()

        # Нижний разделитель и строка состояния:
        lower_sep = ttk.Separator(root, orient='horizontal')
        lower_sep.grid(column=0, columnspan=2, row=2, sticky='WE',
                       padx=(3, 3), pady=(0, 0))

        self.statetxt = tk.StringVar()
        self.statetxt.set("Ожидание")
        self.state = ttk.Label(root, textvariable=self.statetxt,
                               anchor='w', width=txtwidth)
        self.state.grid(column=0, row=3, columnspan=2, sticky='WE',
                        padx=(8, 8), pady=(3, 3))

    def _reset_rframe(self):
        if self.core.cert_keys:
            self.cselect.set('Выберите сертификат')
        else:
            self.cselect.set('Импортируйте сертификаты')
        self.certinfotxt.set("Сертификат не выбран")

    def _add_files(self):
        filetypes = [('Все типы файлов из настроек',
                      ['*.%s' % e for e in self.core.file_ext])]
        filetypes += [('Файлы %s' % (e.upper()), '*.%s' % e)
                      for e in self.core.file_ext]
        fpaths = fd.askopenfilenames(initialdir='.', filetypes=filetypes,
                                     title='Выбор файлов для подписи')
        for item in fpaths:
            fp = Path(item)
            if self.core.add_to_flist(fp):
                fstat = self.core.fstat[fp]
                self.tree.insert('', 'end', fp, text=item, tags=fstat,
                                 values=(status_tips[fstat], ))
            else:
                mb.showwarning(title='Повторное добавление файла',
                               message='Этот файл уже в списке:\n\n%s' % fp)

    def _item_select(self, event):
        new_selected_flist = []
        for item in self.tree.selection():
            sp = Path(item)
            new_selected_flist.append(sp)
        self.core.selected_flist.clear()
        self.core.selected_flist.extend(new_selected_flist)

    def _setup(self):
        SettingsWindow(self, self.icon)

    def _select_all_files(self):
        for item in self.tree.get_children():
            self.tree.selection_add(item)

    def _unselect_all_files(self):
        for item in self.tree.selection():
            self.tree.selection_remove(item)

    def _del_selected_files(self):
        for item in self.tree.selection():
            fp = Path(item)
            self.core.remove_from_flist(fp)
            self.tree.delete(item)

    def _clear_list(self):
        for item in self.tree.get_children():
            fp = Path(item)
            self.core.remove_from_flist(fp)
            self.tree.delete(item)

    def _import_cert(self):
        certpath = fd.askopenfilename(defaultextension='.cer',
                                      filetypes=[('Сертификаты ЭЦП',
                                                  '*.cer')],
                                      initialdir='.',
                                      title='Импорт сертификата ЭЦП')
        if certpath == tuple():
            return
        try:
            cert_info = self.core.import_certiticate(certpath)
        except KeyError:
            mb.showwarning(title='Повторное добавление сертификата',
                           message='Сертификат уже импортирован ранее')
        except Exception:
            traceback_path = write_traceback()
            msg = 'Ошибка импорта сертификата, сохранен %s' % traceback_path
            mb.showerror(title='Ошибка импорта',
                         message=msg)
        else:
            ImportCertWindow(self, self.icon, cert_info)
            if hasattr(cert_info, 'approved'):
                if cert_info.approved:
                    self.core.append_storage(certpath, cert_info)
                    self.cselect['values'] = self.core.cert_keys
                    self.cselect.set(cert_info.ckey)
                    self._cert_select(None)

    def _cert_select(self, event):
        ckey = self.cselect.get()
        self.core.select_certificate(ckey)
        self.certinfotxt.set(self.core.selected_cert_digest)

    def _del_cert(self):
        if self.core.selected_cert is None:
            return
        ckey = self.cselect.get()
        seriously = mb.askyesno(title="Подтверждение удаления сертификата",
                                message="Удалить сертификат:\n%s?" % ckey)
        if not seriously:
            return
        self.core.remove_certiticate(ckey)
        self._reset_rframe()
        self.cselect['values'] = self.core.cert_keys

    def _show_help(self):
        webbrowser.open('Руководство пользователя.pdf', new=0, autoraise=True)

    def _show_about_window(self):
        msg = ('%s, версия %s\n\n'
               'Программа разработана в \n'
               'МБУ ДО «Таганрогская школа искусств» \n'
               'http://art-school-taganrog.narod.ru/\n\n'
               'Разрешается свободное некоммерческое\n'
               'использование. \n'
               'Разработчик не несёт ответственности \n'
               'за любые последствия применения программы.')
        mb.showinfo(title='О программе',
                    message=msg % (__progname__, __version__))

    def _sign_files(self):
        try:
            for fpath, result in self.core.sign_list_check():
                if not result:
                    title = "Перезапись ЭЦП"
                    msg = ('Файл:\n\n%s\n\nуже подписан.\n\n'
                           'Удалить подпись и подписать заново?')
                    seriously = mb.askyesno(title=title,
                                            message=msg % fpath)
                    if not seriously:
                        self.tree.selection_remove(fpath)
        except CertificateNotSelected:
            if self.core.cert_db:
                msg = 'Выберите сертификат из списка'
            else:
                msg = 'Импортируйте сертификат для формирования ЭЦП'
            mb.showwarning(title='Не выбран сертификат для формирования ЭЦП',
                           message=msg)
            return
        if self.core.n_selected_files == 0:
            mb.showwarning(title='Нет файлов для подписи',
                           message='Выделите файлы для подписи')
        else:
            FileOperationsWindow(self, self.icon, 'sign')
            for fpath in self.core.selected_flist:
                fstat = self.core.fstat[fpath]
                self.tree.item(fpath, tags=fstat,
                               values=(status_tips[fstat], ))
            for fpath in self.core.error_items:
                self.tree.item(fpath, tags='JSON_MISSING',
                               values=('Ошибка подписи', ))

    def _check_signs(self):
        for fpath in self.core.selected_flist:
            fstat = self.core.fstat[fpath]
            if fstat == 'NOT_SIGNED':
                msg = 'Этот файл не имеет открепленной ЭЦП:\n\n%s'
                mb.showwarning(title='Файл без ЭЦП', message=msg % fpath)
                self.tree.selection_remove(fpath)
        if self.core.selected_cert is None:
            if self.core.cert_db:
                msg = 'Выберите сертификат ЭЦП, которой подписан файл'
            else:
                msg = 'Импортируйте сертификат ЭЦП, которой подписан файл'
            mb.showwarning(title='Не выбран сертификат для проверки ЭЦП',
                           message=msg)
        else:
            FileOperationsWindow(self, self.icon, 'check')
            for fpath in self.core.selected_flist:
                self.tree.item(fpath, tags='SIGNED',
                               values=('ЭЦП проверена', ))
            for fpath in self.core.error_items:
                self.tree.item(fpath, tags='JSON_MISSING',
                               values=('Ошибка проверки ЭЦП', ))

    def _unsign_files(self):
        seriously = mb.askyesno(title="Подтвердите удаление ЭЦП",
                                message="Удалить ЭЦП выбранных файлов?")
        if not seriously:
            return
        for fpath, result in self.core.remove_sign():
            if not result:
                msg = 'Файл:\n\n%s\n\nне подписан'
                mb.showwarning(title='Невозможно удалить ЭЦП',
                               message=msg % fpath)
                continue
            fstat = self.core.fstat[fpath]
            self.tree.item(fpath, tags=fstat, values=(status_tips[fstat], ))


class ImportCertWindow(tk.Toplevel):
    """ Окно импорта сертификата ЭЦП.
    """

    def __init__(self, main_win, icon, cert_info):
        super().__init__(main_win.root)

        self.cert_info = cert_info
        self.main_win = main_win

        self.iconphoto(False, icon)
        self.title('Импорт сертификата')
        self.geometry('800x640')
        self.grid_columnconfigure(0, weight=1)
        self.grid_rowconfigure(1, weight=1)

        self.ufrm = ttk.Frame(self, padding=(16, 16, 24, 0))
        self.ufrm.grid(column=0, row=0, sticky='NW')
        txt = "Внесите дополнительные сведения о сертификате:"
        label = ttk.Label(self.ufrm, text=txt)
        label.grid(column=0, row=0, sticky='W',
                   padx=(8, 8), pady=(8, 8))

        self.frm = ttk.Frame(self, padding=(16, 16, 24, 0))
        self.frm.grid(column=0, row=1, sticky='NSWE')
        self.frm.grid_columnconfigure(1, weight=1)

        # Фамилия (surname, SN):
        surname_label = ttk.Label(self.frm, text="Фамилия:")
        surname_label.grid(column=0, row=0, sticky='W',
                           padx=(8, 8), pady=(8, 8))
        surname_value = ttk.Entry(self.frm)
        surname_value.grid(column=1, row=0, sticky='WE',
                           padx=(8, 8), pady=(8, 8))
        surname_value.insert(0, cert_info.surname)
        surname_value.config(state="readonly")

        # Имя и отчество (given name, GN):
        given_name_label = ttk.Label(self.frm, text="Имя Отчество:")
        given_name_label.grid(column=0, row=1, sticky='W',
                              padx=(8, 8), pady=(8, 8))
        given_name_value = ttk.Entry(self.frm)
        given_name_value.grid(column=1, row=1, sticky='WE',
                              padx=(8, 8), pady=(8, 8))
        given_name_value.insert(0, cert_info.given_name)
        given_name_value.config(state="readonly")

        # И. О.:
        gn_label = ttk.Label(self.frm, text="И. О.:")
        gn_label.grid(column=0, row=2, sticky='W',
                      padx=(8, 8), pady=(8, 8))
        self.gn_value = ttk.Entry(self.frm)
        self.gn_value.grid(column=1, row=2, sticky='WE',
                           padx=(8, 8), pady=(8, 8))
        self.gn_value.insert(0, cert_info.gn)

        # Должность:
        post_label = ttk.Label(self.frm, text="Должность:")
        post_label.grid(column=0, row=3, sticky='W',
                        padx=(8, 8), pady=(8, 8))
        self.post_value = ttk.Entry(self.frm)
        self.post_value.grid(column=1, row=3, sticky='WE',
                             padx=(8, 8), pady=(8, 8))

        # Организация:
        organization_label = ttk.Label(self.frm, text="Организация:")
        organization_label.grid(column=0, row=4, sticky='W',
                                padx=(8, 8), pady=(8, 8))
        organization_value = ttk.Entry(self.frm)
        organization_value.grid(column=1, row=4, sticky='WE',
                                padx=(8, 8), pady=(8, 8))
        organization_value.insert(0, cert_info.organization)
        organization_value.config(state="readonly")

        # Организация (кратко):
        organization_alt_label = ttk.Label(self.frm,
                                           text="Организация (кратко):")
        organization_alt_label.grid(column=0, row=5, sticky='W',
                                    padx=(8, 8), pady=(8, 8))
        self.organization_alt_value = ttk.Entry(self.frm)
        self.organization_alt_value.grid(column=1, row=5, sticky='WE',
                                         padx=(8, 8), pady=(8, 8))
        self.organization_alt_value.insert(0, cert_info.organization_alt)

        # Электронная почта:
        email_label = ttk.Label(self.frm, text="Электронная почта:")
        email_label.grid(column=0, row=6, sticky='W',
                         padx=(8, 8), pady=(8, 8))
        email_value = ttk.Entry(self.frm)
        email_value.grid(column=1, row=6, sticky='WE',
                         padx=(8, 8), pady=(8, 8))
        email_value.insert(0, cert_info.email)
        email_value.config(state="readonly")

        # Cерийный номер:
        serial_label = ttk.Label(self.frm, text="Cерийный номер:")
        serial_label.grid(column=0, row=7, sticky='W',
                          padx=(8, 8), pady=(8, 8))
        serial_value = ttk.Entry(self.frm)
        serial_value.grid(column=1, row=7, sticky='WE',
                          padx=(8, 8), pady=(8, 8))
        serial_value.insert(0, cert_info.serial)
        serial_value.config(state="readonly")

        # Хэш SHA1:
        sha1_label = ttk.Label(self.frm, text="Хэш SHA1:")
        sha1_label.grid(column=0, row=8, sticky='W',
                        padx=(8, 8), pady=(8, 8))
        sha1_value = ttk.Entry(self.frm)
        sha1_value.grid(column=1, row=8, sticky='WE',
                        padx=(8, 8), pady=(8, 8))
        sha1_value.insert(0, cert_info.sha1)
        sha1_value.config(state="readonly")

        # Алгоритм:
        algorithm_label = ttk.Label(self.frm, text="Алгоритм:")
        algorithm_label.grid(column=0, row=9, sticky='W',
                             padx=(8, 8), pady=(8, 8))
        algorithm_value = ttk.Entry(self.frm)
        algorithm_value.grid(column=1, row=9, sticky='WE',
                             padx=(8, 8), pady=(8, 8))
        algorithm_value.insert(0, cert_info.algorithm)
        algorithm_value.config(state="readonly")

        # Срок действия:
        period_label = ttk.Label(self.frm, text="Срок действия:")
        period_label.grid(column=0, row=10, sticky='W',
                          padx=(8, 8), pady=(8, 8))
        period_value = ttk.Entry(self.frm)
        period_value.grid(column=1, row=10, sticky='WE',
                          padx=(8, 8), pady=(8, 8))
        period_value.insert(0, '%s — %s' % (cert_info.period_start,
                                            cert_info.period_end))
        period_value.config(state="readonly")

        # Кнопки
        self.lfrm = ttk.Frame(self, padding=(4, 4, 4, 0))
        self.lfrm.grid(column=0, row=2, sticky='SE')
        self.lfrm.grid_columnconfigure(0, weight=1)

        self.okbtn = ttk.Button(self.lfrm, text='OK',
                                command=self._update_cert_info,
                                state='enabled', width=16)
        self.okbtn.grid(column=0, row=11, sticky='SE',
                        padx=(8, 8), pady=(8, 8))

        self.cancelbtn = ttk.Button(self.lfrm, text='Отмена',
                                    command=self._cancel,
                                    state='enabled', width=16)
        self.cancelbtn.grid(column=1, row=11, sticky='SE',
                            padx=(8, 8), pady=(8, 8))

        # Set to be on top of the main window:
        self.transient(main_win.root)
        # Рijack all commands from the master (clicks on the main window are
        # ignored):
        self.grab_set()
        # Pause anything on the main window until this one closes:
        main_win.root.wait_window(self)

    def _update_cert_info(self):
        if not self.post_value.get():
            mb.showwarning(title='Поле "Должность" не заполнено',
                           message='Заполните поле "Должность"')
            return
        self.cert_info.update('gn', self.gn_value.get())
        self.cert_info.update('post', self.post_value.get())
        self.cert_info.update('organization_alt',
                              self.organization_alt_value.get())
        self.cert_info.approved = True
        self.main_win.root.lift(self)
        self.destroy()
        return

    def _cancel(self):
        self.main_win.root.lift(self)
        self.destroy()
        return


class SettingsWindow(tk.Toplevel):
    """ Окно настроек программы.

    """
    def __init__(self, main_win, icon):
        super().__init__(main_win.root)

        self.main_win = main_win
        self.core = main_win.core

        self.iconphoto(False, icon)
        self.title('Настройки программы')
        self.geometry('640x640')
        self.grid_columnconfigure(0, weight=1)
        self.grid_rowconfigure(0, weight=1)

        self.frm = ttk.Frame(self, padding=(5, 5, 5, 5), relief=tk.GROOVE)
        self.frm.grid(column=0, row=0, sticky='NSWE')
        self.frm.grid_columnconfigure(0, weight=1)

        # Путь к программе cryptcp:
        row = 0
        crypt_path_label = ttk.Label(self.frm,
                                     text="Путь к программе cryptcp:")
        crypt_path_label.grid(column=0, row=row, sticky='SW',
                              padx=(8, 8), pady=(8, 8))
        self.crypt_path = ttk.Entry(self.frm)
        row += 1
        self.crypt_path.grid(column=0, row=row, sticky='NWE', columnspan=2,
                             padx=(8, 8), pady=(8, 8))
        self.crypt_path.insert(0, self.core.crypt_path)
        row += 1
        crypt_path_btn = ttk.Button(self.frm, text='Обзор...',
                                    command=self._browse_crypt_path,
                                    state='enabled', width=16)
        crypt_path_btn.grid(column=1, row=row, sticky='SE',
                            padx=(8, 8), pady=(8, 8))

        # Аргументы для передачи программе cryptcp:
        row += 1
        txt = 'Аргументы программы cryptcp для подписи файлов:'
        crypt_args_sign_label = ttk.Label(self.frm, text=txt)
        crypt_args_sign_label.grid(column=0, row=row, sticky='SW',
                                   padx=(8, 8), pady=(8, 8))
        row += 1
        self.crypt_args_sign = ttk.Entry(self.frm)
        self.crypt_args_sign.grid(column=0, row=row, sticky='NWE',
                                  columnspan=2,
                                  padx=(8, 8), pady=(8, 8))
        self.crypt_args_sign.insert(0, self.core.crypt_args_sign)

        row += 1
        txt = 'Аргументы программы cryptcp для проверки подписи:'
        crypt_args_verify_label = ttk.Label(self.frm, text=txt)
        crypt_args_verify_label.grid(column=0, row=row, sticky='SW',
                                     padx=(8, 8), pady=(8, 8))
        row += 1
        self.crypt_args_verify = ttk.Entry(self.frm)
        self.crypt_args_verify.grid(column=0, row=row, sticky='NWE',
                                    columnspan=2,
                                    padx=(8, 8), pady=(8, 8))
        self.crypt_args_verify.insert(0, self.core.crypt_args_verify)

        row += 1
        txt = 'Кодировка символов командной строки:'
        console_encoding_label = ttk.Label(self.frm, text=txt)
        console_encoding_label.grid(column=0, row=row, sticky='SW',
                                    padx=(8, 8), pady=(8, 8))
        row += 1
        self.console_encoding = ttk.Entry(self.frm)
        self.console_encoding.grid(column=0, row=row, sticky='NWE',
                                   columnspan=2,
                                   padx=(8, 8), pady=(8, 8))
        self.console_encoding.insert(0, self.core.console_encoding)

        row += 1
        sep1 = ttk.Separator(self.frm, orient='horizontal')
        sep1.grid(column=0, columnspan=2, row=row, sticky='WE',
                  padx=(3, 3), pady=(3, 3))

        # Расширение файлов откреплённой электронной подписи:
        row += 1
        txt = 'Расширение файлов откреплённой электронной подписи:'
        crypt_path_label = ttk.Label(self.frm, text=txt)
        crypt_path_label.grid(column=0, row=row, sticky='SW',
                              padx=(8, 8), pady=(8, 8))

        row += 1
        self.sign_ext = tk.StringVar()
        sign_ext_radiobtn1 = ttk.Radiobutton(self.frm, text='*.sig',
                                             value='sig',
                                             variable=self.sign_ext)
        sign_ext_radiobtn1.grid(column=0, row=row, sticky='N',
                                padx=(8, 8), pady=(8, 8))
        row += 1
        sign_ext_radiobtn2 = ttk.Radiobutton(self.frm, text='*.sgn',
                                             value='sgn',
                                             variable=self.sign_ext)
        sign_ext_radiobtn2.grid(column=0, row=row, sticky='N',
                                padx=(8, 8), pady=(8, 8))
        if self.core.sign_ext == 'sig':
            sign_ext_radiobtn1.invoke()
        elif self.core.sign_ext == 'sgn':
            sign_ext_radiobtn2.invoke()
        else:
            msg = 'Неизвестное значение self.core.sign_ext = %s'
            mb.showerror(title='Ошибка расширения файла',
                         message=msg % self.core.sign_ext)

        row += 1
        sep2 = ttk.Separator(self.frm, orient='horizontal')
        sep2.grid(column=0, columnspan=2, row=row, sticky='WE',
                  padx=(3, 3), pady=(3, 3))

        # Расширения подписываемых файлов:
        row += 1
        file_ext_label = ttk.Label(self.frm,
                                   text="Расширения подписываемых файлов:")
        file_ext_label.grid(column=0, row=row, sticky='SW',
                            padx=(8, 8), pady=(8, 8))
        row += 1
        self.file_ext = ttk.Entry(self.frm)
        self.file_ext.grid(column=0, row=row, sticky='NWE', columnspan=2,
                           padx=(8, 8), pady=(8, 8))
        self.file_ext.insert(0, ', '.join(self.core.file_ext))

        row += 1
        sep3 = ttk.Separator(self.frm, orient='horizontal')
        sep3.grid(column=0, columnspan=2, row=row, sticky='WE',
                  padx=(3, 3), pady=(3, 3))

        # Таймаут файловых операций:
        row += 1
        timeout_label = ttk.Label(self.frm,
                                  text="Таймаут файловых операций, c:")
        timeout_label.grid(column=0, row=row, sticky='SW',
                           padx=(8, 8), pady=(8, 8))
        row += 1
        self.timeout = ttk.Entry(self.frm)
        self.timeout.grid(column=0, row=row, sticky='N',
                          padx=(8, 8), pady=(8, 8))
        self.timeout.insert(0, str(self.core.timeout))

        # Кнопки
        self.lfrm = ttk.Frame(self, padding=(4, 4, 4, 0))
        self.lfrm.grid(column=0, row=2, sticky='SE')
        self.lfrm.grid_columnconfigure(0, weight=1)

        self.okbtn = ttk.Button(self.lfrm, text='Сохранить',
                                command=self._update_settings,
                                state='enabled', width=16)
        self.okbtn.grid(column=0, row=11, sticky='SE',
                        padx=(8, 8), pady=(8, 8))

        self.cancelbtn = ttk.Button(self.lfrm, text='Отмена',
                                    command=self._cancel,
                                    state='enabled', width=16)
        self.cancelbtn.grid(column=1, row=11, sticky='SE',
                            padx=(8, 8), pady=(8, 8))

        # Set to be on top of the main window:
        self.transient(main_win.root)
        # Рijack all commands from the master (clicks on the main window are
        # ignored):
        self.grab_set()
        # Pause anything on the main window until this one closes:
        main_win.root.wait_window(self)

    def _browse_crypt_path(self):
        title = 'Указать путь к программе cryptcp'
        crypt_path = fd.askopenfilename(initialdir='.', title=title)
        self.crypt_path.delete(0, 'end')
        self.crypt_path.insert(0, crypt_path)

    def _update_settings(self):
        file_ext_string = self.file_ext.get()
        file_ext = tuple(e.strip() for e in file_ext_string.split(','))
        crypt_path = self.crypt_path.get()
        crypt_args_sign = self.crypt_args_sign.get()
        crypt_args_verify = self.crypt_args_verify.get()
        self.core.update_settings(crypt_path=crypt_path,
                                  crypt_args_sign=crypt_args_sign,
                                  crypt_args_verify=crypt_args_verify,
                                  sign_ext=self.sign_ext.get(),
                                  file_ext=file_ext,
                                  timeout=self.timeout.get())
        self.main_win.root.lift(self)
        self.destroy()
        return

    def _cancel(self):
        self.main_win.root.lift(self)
        self.destroy()
        return


class FileOperationsWindow(tk.Toplevel):

    def __init__(self, main_win, icon, task):
        super().__init__(main_win.root)

        self.main_win = main_win
        self.core = main_win.core

        self.iconphoto(False, icon)
        self.title('Выполнение операций с ЭЦП')
        # self.geometry('560x240')
        self.grid_columnconfigure(0, weight=1)
        self.grid_rowconfigure(0, weight=1)

        self.frm = ttk.Frame(self, padding=(5, 5, 5, 5), relief=tk.GROOVE)
        self.frm.grid(column=0, row=0, sticky='NSWE')
        self.frm.grid_columnconfigure(0, weight=1)

        self.status_txt = tk.StringVar()
        txt = 'Выполнено 0 из %d, ошибок 0' % self.core.n_selected_files
        self.status_txt.set(txt)
        self.status_label = ttk.Label(self.frm,
                                      textvariable=self.status_txt,
                                      anchor='w')
        self.status_label.grid(column=0, row=0, sticky='NWE',
                               padx=(8, 8), pady=(4, 4))

        self.progr_var = tk.DoubleVar()
        self.progr_var.set(0.0)

        self.progr_bar = ttk.Progressbar(self.frm, orient="horizontal",
                                         mode="determinate", maximum=1.0,
                                         variable=self.progr_var,
                                         length=560)
        self.progr_bar.grid(column=0, row=1, sticky='NWE',
                            padx=(8, 8), pady=(4, 4))

        self.statetxt = tk.StringVar()
        self.statetxt.set("Отменить")
        self.stop_pressed = False
        self.btn = ttk.Button(self.frm, textvariable=self.statetxt,
                              command=self._stop,
                              state='enabled', width=16)
        self.btn.grid(column=0, row=3, sticky='SE',
                      padx=(8, 8), pady=(8, 8))

        # Set to be on top of the main window:
        self.transient(main_win.root)

        # Рijack all commands from the master (clicks on the main window are
        # ignored):
        self.grab_set()

        if task == 'sign':
            self.sign_process()
        elif task == 'check':
            self.check_process()
        else:
            raise ValueError('Неизвестная задача: %s' % task)

    def _stop(self):
        self.stop_pressed = True

    def _close(self):
        self.main_win.root.lift(self)
        self.destroy()
        return

    def sign_process(self):
        self.update()
        msg = 'Подписано файлов: %d из %d, ошибок %d'
        for ndone, nerr, ntotal in self.core.sign_selected_files():
            if self.stop_pressed:
                self._close()
            self.status_txt.set(msg % (ndone, ntotal, nerr))
            self.progr_var.set((ndone+nerr)/ntotal)
            self.update()
        self._close()

    def check_process(self):
        self.update()
        msg = 'Проверено ЭЦП: %d из %d, ошибок %d'
        print('p1')
        for ndone, nerr, ntotal in self.core.check_selected_signs():
            print('p2')
            if self.stop_pressed:
                self._close()
            self.status_txt.set(msg % (ndone, ntotal, nerr))
            self.progr_var.set((ndone+nerr)/ntotal)
            self.update()
        self._close()
