'''

Revealer
Do you have something to hide?
Secret backup plug-in for the electrum wallet.

Copyright:
    2017 Tiago Romagnani Silveira
    2023-2024 Soren Stoutner <soren@debian.org>

Distributed under the MIT software license, see the accompanying
file LICENCE or http://www.opensource.org/licenses/mit-license.php

'''

import os
import random
import traceback
from decimal import Decimal
from functools import partial
import sys
from typing import TYPE_CHECKING

import qrcode
from PyQt5.QtPrintSupport import QPrinter
from PyQt5.QtCore import Qt, QRectF, QRect, QSizeF, QUrl, QPoint, QSize
from PyQt5.QtGui import (QPixmap, QImage, QBitmap, QPainter, QFontDatabase, QPen, QFont,
                         QColor, QDesktopServices, qRgba, QPainterPath)
from PyQt5.QtWidgets import (QGridLayout, QVBoxLayout, QHBoxLayout, QLabel,
                             QPushButton, QLineEdit)

from electrum.plugin import hook
from electrum.i18n import _
from electrum.util import make_dir, InvalidPassword, UserCancelled
from electrum.gui.qt.util import (read_QIcon, EnterButton, WWLabel, icon_path,
                                  WindowModalDialog, Buttons, CloseButton, OkButton)
from electrum.gui.qt.qrtextedit import ScanQRTextEdit
from electrum.gui.qt.main_window import StatusBarButton

from .revealer import RevealerPlugin


if TYPE_CHECKING:
    from electrum.gui.qt import ElectrumGui


class Plugin(RevealerPlugin):

    MAX_PLAINTEXT_LEN = 189  # chars

    def __init__(self, parent, config, name):
        RevealerPlugin.__init__(self, parent, config, name)
        self.base_dir = os.path.join(config.electrum_path(), 'revealer')

        if self.config.get('calibration_h') is None:
            self.config.set_key('calibration_h', 0)
        if self.config.get('calibration_v') is None:
            self.config.set_key('calibration_v', 0)

        self.calibration_h = self.config.get('calibration_h')
        self.calibration_v = self.config.get('calibration_v')

        self.f_size = QSize(1014*2, 642*2)
        self.abstand_h = 21
        self.abstand_v = 34
        self.calibration_noise = int('10' * 128)
        self.rawnoise = False
        make_dir(self.base_dir)

        self.extension = False
        self._init_qt_received = False

    @hook
    def init_qt(self, gui: 'ElectrumGui'):
        if self._init_qt_received:  # only need/want the first signal
            return
        self._init_qt_received = True
        # load custom fonts (note: here, and not in __init__, as it needs the QApplication to be created)
        QFontDatabase.addApplicationFont(os.path.join(os.path.dirname(__file__), 'SourceSans3-Bold.otf'))
        QFontDatabase.addApplicationFont(os.path.join(os.path.dirname(__file__), 'DejaVuSansMono-Bold.ttf'))

    @hook
    def create_status_bar(self, sb):
        b = StatusBarButton(read_QIcon('revealer.png'), "Revealer "+_("Visual Cryptography Plugin"),
                            partial(self.setup_dialog, sb), sb.height())
        sb.addPermanentWidget(b)

    def requires_settings(self):
        return True

    def settings_widget(self, window):
        return EnterButton(_('Printer Calibration'), partial(self.calibration_dialog, window))

    def password_dialog(self, msg=None, parent=None):
        from electrum.gui.qt.password_dialog import PasswordDialog
        parent = parent or self
        d = PasswordDialog(parent, msg)
        return d.run()

    def get_seed(self):
        password = None
        if self.wallet.has_keystore_encryption():
            password = self.password_dialog(parent=self.d.parent())
            if not password:
                raise UserCancelled()

        keystore = self.wallet.get_keystore()
        if not keystore or not keystore.has_seed():
            return
        self.extension = bool(keystore.get_passphrase(password))
        return keystore.get_seed(password)

    def setup_dialog(self, window):
        self.wallet = window.parent().wallet
        self.update_wallet_name(self.wallet)
        self.user_input = False

        self.d = WindowModalDialog(window, "Revealer Visual Cryptography Plugin - Select Noise File")
        self.d.setContentsMargins(11,11,1,1)

        # Create an HBox layout.  The logo will be on the left and the rest of the dialog on the right.
        hbox_layout = QHBoxLayout(self.d)

        # Create the logo label.
        logo_label = QLabel()

        # Set the logo label pixmap.
        logo_label.setPixmap(QPixmap(icon_path('revealer.png')))

        # Align the logo label to the top left.
        logo_label.setAlignment(Qt.AlignLeft)

        # Create a VBox layout for the main contents of the dialog.
        vbox_layout = QVBoxLayout()

        # Populate the HBox layout with spacing between the two columns.
        hbox_layout.addWidget(logo_label)
        hbox_layout.addSpacing(16)
        hbox_layout.addLayout(vbox_layout)

        # Create the labels.
        create_or_load_noise_file_label = QLabel(_("To encrypt a secret, you must first create or load a noise file."))
        instructions_label = QLabel(_("Click the button above or type an existing revealer code in the box below."))

        # Allow users to select text in the labels.
        create_or_load_noise_file_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
        instructions_label.setTextInteractionFlags(Qt.TextSelectableByMouse)

        # Create the buttons.
        create_button = QPushButton(_("Create a new Revealer noise file"))
        self.next_button = QPushButton(_("Next"), self.d)

        # Calculate the desired width of the create button
        create_button_width = create_button.fontMetrics().boundingRect(create_button.text()).width() + 40

        # Set the create button width.
        create_button.setMaximumWidth(create_button_width)

        # Set the create button to be the default.
        create_button.setDefault(True)

        # Initially disable the next button.
        self.next_button.setEnabled(False)

        # Define the create noise file function.
        def create_noise_file():
            try:
                self.make_digital(self.d)
            except Exception:
                self.logger.exception('')
            else:
                self.cypherseed_dialog(window)

        # Handle clicks on the buttons.
        create_button.clicked.connect(create_noise_file)
        self.next_button.clicked.connect(self.d.close)
        self.next_button.clicked.connect(partial(self.cypherseed_dialog, window))

        # Create the noise scan QR text edit.
        self.noise_scan_qr_textedit = ScanQRTextEdit(config=self.config)

        # Make tabs change focus from the text edit instead of inserting a tab into the field.
        self.noise_scan_qr_textedit.setTabChangesFocus(True)

        # Update the UI when the text changes.
        self.noise_scan_qr_textedit.textChanged.connect(self.on_edit)

        # Populate the VBox layout.
        vbox_layout.addWidget(create_or_load_noise_file_label)
        vbox_layout.addWidget(create_button, alignment=Qt.AlignCenter)
        vbox_layout.addWidget(instructions_label)
        vbox_layout.addWidget(self.noise_scan_qr_textedit)
        vbox_layout.addLayout(Buttons(self.next_button))

        # Add stretches to the end of the layouts to prevent the contents from spreading when the dialog is enlarged.
        hbox_layout.addStretch(1)
        vbox_layout.addStretch(1)

        return bool(self.d.exec_())

    def get_noise(self):
        # Get the text from the scan QR text edit.
        text = self.noise_scan_qr_textedit.text()
        return ''.join(text.split()).lower()

    def on_edit(self):
        txt = self.get_noise()
        versioned_seed = self.get_versioned_seed_from_user_input(txt)
        if versioned_seed:
            self.versioned_seed = versioned_seed
        self.user_input = bool(versioned_seed)
        self.next_button.setEnabled(bool(versioned_seed))

    def make_digital(self, dialog):
        self.make_rawnoise(True)
        self.bdone(dialog)
        self.d.close()

    def get_path_to_revealer_file(self, ext: str= '') -> str:
        version = self.versioned_seed.version
        code_id = self.versioned_seed.checksum
        filename = self.filename_prefix + version + "_" + code_id + ext
        path = os.path.join(self.base_dir, filename)
        return os.path.normcase(os.path.abspath(path))

    def get_path_to_calibration_file(self):
        path = os.path.join(self.base_dir, 'calibration.pdf')
        return os.path.normcase(os.path.abspath(path))

    def bcrypt(self, dialog):
        self.rawnoise = False
        version = self.versioned_seed.version
        code_id = self.versioned_seed.checksum
        dialog.show_message(''.join([_("{} encrypted for Revealer {}_{} saved as PNG and PDF at: ").format(self.was, version, code_id),
                                     "<b>", self.get_path_to_revealer_file(), "</b>", "<br/>",
                                     "<br/>", "<b>", _("Always check your backups.")]),
                            rich_text=True)
        dialog.close()

    def ext_warning(self, dialog):
        dialog.show_message(''.join(["<b>",_("Warning"), ": </b>",
                                     _("your seed extension will <b>not</b> be included in the encrypted backup.")]),
                            rich_text=True)
        dialog.close()

    def bdone(self, dialog):
        version = self.versioned_seed.version
        code_id = self.versioned_seed.checksum
        dialog.show_message(''.join([_("Digital Revealer ({}_{}) saved as PNG and PDF at:").format(version, code_id),
                                     "<br/>","<b>", self.get_path_to_revealer_file(), '</b>']),
                            rich_text=True)


    def customtxt_limits(self):
        txt = self.custom_secret_scan_qr_textedit.text()
        self.custom_secret_character_count_label.setText(f"({len(txt)}/{self.MAX_PLAINTEXT_LEN})")

        # Hide the custom secret maximum characters warning label.
        self.custom_secret_maximum_characters_warning_label.setVisible(False)

        # Update the status of the encrypt custom secret button.
        self.encrypt_custom_secret_button.setEnabled(len(txt)>0)

        # Check to make sure the length of the text has not exceeded the limit.
        if len(txt) > self.MAX_PLAINTEXT_LEN:
            # Truncate the text to the maximum limit.
            self.custom_secret_scan_qr_textedit.setPlainText(txt[:self.MAX_PLAINTEXT_LEN])

            # Get the text cursor.
            textCursor = self.custom_secret_scan_qr_textedit.textCursor()

            # Move the cursor position to the end (setting the text above automatically moves the cursor to the beginning, which is undesirable)
            textCursor.movePosition(textCursor.End)

            # Set the text cursor with the corrected position.
            self.custom_secret_scan_qr_textedit.setTextCursor(textCursor)

            # Display the custom secret maximum characters warning label.
            self.custom_secret_maximum_characters_warning_label.setVisible(True)

    def t(self):
        self.txt = self.custom_secret_scan_qr_textedit.text()
        self.seed_img(is_seed=False)

    def warn_old_revealer(self):
        if self.versioned_seed.version == '0':
            link = "https://revealer.cc/revealer-warning-and-upgrade/"
            self.d.show_warning(("<b>{warning}: </b>{ver0}<br>"
                                 "{url}<br>"
                                 "{risk}")
                                .format(warning=_("Warning"),
                                        ver0=_("Revealers starting with 0 are not secure due to a vulnerability."),
                                        url=_("More info at: {}").format(f'<a href="{link}">{link}</a>'),
                                        risk=_("Proceed at your own risk.")),
                                rich_text=True)

    def cypherseed_dialog(self, window):
        self.warn_old_revealer()

        d = WindowModalDialog(window, "Revealer Visual Cryptography Plugin - Encryption Data")
        d.setContentsMargins(11, 11, 1, 1)
        self.c_dialog = d

        # Create an HBox layout.  The logo will be on the left and the rest of the dialog on the right.
        hbox_layout = QHBoxLayout(d)

        # Create the logo label.
        logo_label = QLabel()

        # Set the logo label pixmap.
        logo_label.setPixmap(QPixmap(icon_path('revealer.png')))

        # Align the logo label to the top left.
        logo_label.setAlignment(Qt.AlignLeft)

        # Create a VBox layout for the main contents of the dialog.
        vbox_layout = QVBoxLayout()

        # Populate the HBox layout.
        hbox_layout.addWidget(logo_label)
        hbox_layout.addSpacing(16)
        hbox_layout.addLayout(vbox_layout)

        # Create the labels.
        ready_to_encrypt_label = QLabel(_("Ready to encrypt for revealer {}.").format(self.versioned_seed.version+'_'+self.versioned_seed.checksum))
        instructions_label = QLabel(_("Click the button above to encrypt the seed or type a custom alphanumerical secret below."))
        self.custom_secret_character_count_label = QLabel(f"(0/{self.MAX_PLAINTEXT_LEN})")
        self.custom_secret_maximum_characters_warning_label = QLabel("<font color='red'>"
                                                       + _("This version supports a maximum of {} characters.").format(self.MAX_PLAINTEXT_LEN)
                                                       +"</font>")
        one_time_pad_warning_label = QLabel("<b>" + _("Warning ") + "</b>: " + _("each Revealer is a one-time-pad, use it for a single secret."))

        # Allow users to select text in the labels.
        ready_to_encrypt_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
        instructions_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
        self.custom_secret_character_count_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
        self.custom_secret_maximum_characters_warning_label
        one_time_pad_warning_label.setTextInteractionFlags(Qt.TextSelectableByMouse)

        # Align the custom secret character count label to the right.
        self.custom_secret_character_count_label.setAlignment(Qt.AlignRight)

        # Initially hide the custom secret character count label.
        self.custom_secret_maximum_characters_warning_label.setVisible(False)

        # Create the buttons.
        encrypt_seed_button = QPushButton(_("Encrypt {}'s seed").format(self.wallet_name))
        self.encrypt_custom_secret_button = QPushButton(_("Encrypt custom secret"))

        # Calculate the desired width of the buttons.
        encrypt_seed_button_width = encrypt_seed_button.fontMetrics().boundingRect(encrypt_seed_button.text()).width() + 40
        encrypt_custom_secret_button_width = self.encrypt_custom_secret_button.fontMetrics().boundingRect(self.encrypt_custom_secret_button.text()).width() + 40

        # Set the button widths.
        encrypt_seed_button.setMaximumWidth(encrypt_seed_button_width)
        self.encrypt_custom_secret_button.setMaximumWidth(encrypt_custom_secret_button_width)

        # Set the encrypt seed button to be the default.
        encrypt_seed_button.setDefault(True)

        # Initially disable the encrypt custom secret button.
        self.encrypt_custom_secret_button.setEnabled(False)

        # Handle clicks on the buttons.
        encrypt_seed_button.clicked.connect(partial(self.seed_img, True))
        self.encrypt_custom_secret_button.clicked.connect(self.t)

        # Create the custom secret scan QR text edit.
        self.custom_secret_scan_qr_textedit = ScanQRTextEdit(config=self.config)

        # Make tabs change focus from the text edit instead of inserting a tab into the field.
        self.custom_secret_scan_qr_textedit.setTabChangesFocus(True)

        # Update the UI when the custom secret text changes.
        self.custom_secret_scan_qr_textedit.textChanged.connect(self.customtxt_limits)

        # Populate the VBox layout.
        vbox_layout.addWidget(ready_to_encrypt_label)
        vbox_layout.addWidget(encrypt_seed_button, alignment=Qt.AlignCenter)
        vbox_layout.addWidget(instructions_label)
        vbox_layout.addWidget(self.custom_secret_scan_qr_textedit)
        vbox_layout.addWidget(self.custom_secret_character_count_label)
        vbox_layout.addWidget(self.custom_secret_maximum_characters_warning_label)
        vbox_layout.addWidget(self.encrypt_custom_secret_button, alignment=Qt.AlignCenter)
        vbox_layout.addSpacing(40)
        vbox_layout.addWidget(one_time_pad_warning_label)
        vbox_layout.addLayout(Buttons(CloseButton(d)))

        # Add stretches to the end of the layouts to prevent the contents from spreading when the dialog is enlarged.
        hbox_layout.addStretch(1)
        vbox_layout.addStretch(1)

        return bool(d.exec_())

    def update_wallet_name(self, name):
        self.wallet_name = str(name)

    def seed_img(self, is_seed = True):

        if is_seed:
            try:
                cseed = self.get_seed()
            except UserCancelled:
                return
            except InvalidPassword as e:
                self.d.show_error(str(e))
                return
            if not cseed:
                self.d.show_message(_("This wallet has no seed"))
                return
            txt = cseed.upper()
        else:
            txt = self.txt.upper()

        img = QImage(self.SIZE[0], self.SIZE[1], QImage.Format_Mono)
        bitmap = QBitmap.fromImage(img, Qt.MonoOnly)
        bitmap.fill(Qt.white)
        painter = QPainter()
        painter.begin(bitmap)
        if len(txt) < 102 :
            fontsize = 15
            linespace = 15
            max_letters = 17
            max_lines = 6
            max_words = 3
        else:
            fontsize = 12
            linespace = 10
            max_letters = 21
            max_lines = 9
            max_words = int(max_letters/4)

        font = QFont('Source Sans 3', fontsize, QFont.Bold)
        font.setLetterSpacing(QFont.PercentageSpacing, 100)
        font.setPixelSize(fontsize)
        painter.setFont(font)
        seed_array = txt.split(' ')

        for n in range(max_lines):
            nwords = max_words
            temp_seed = seed_array[:nwords]
            while len(' '.join(map(str, temp_seed))) > max_letters:
               nwords = nwords - 1
               temp_seed = seed_array[:nwords]
            painter.drawText(QRect(0, linespace*n, self.SIZE[0], self.SIZE[1]), Qt.AlignHCenter, ' '.join(map(str, temp_seed)))
            del seed_array[:nwords]

        painter.end()
        img = bitmap.toImage()
        if not self.rawnoise:
            self.make_rawnoise()

        self.make_cypherseed(img, self.rawnoise, False, is_seed)
        return img

    def make_rawnoise(self, create_revealer=False):
        if not self.user_input:
            self.versioned_seed = self.gen_random_versioned_seed()
        assert self.versioned_seed
        w, h = self.SIZE
        rawnoise = QImage(w, h, QImage.Format_Mono)

        noise_map = self.get_noise_map(self.versioned_seed)
        for (x,y), pixel in noise_map.items():
            rawnoise.setPixel(x, y, pixel)

        self.rawnoise = rawnoise
        if create_revealer:
            self.make_revealer()

    def make_calnoise(self):
        random.seed(self.calibration_noise)
        w, h = self.SIZE
        rawnoise = QImage(w, h, QImage.Format_Mono)
        for x in range(w):
            for y in range(h):
                rawnoise.setPixel(x,y,random.randint(0, 1))
        self.calnoise = self.pixelcode_2x2(rawnoise)

    def make_revealer(self):
        revealer = self.pixelcode_2x2(self.rawnoise)
        revealer.invertPixels()
        revealer = QBitmap.fromImage(revealer)
        revealer = revealer.scaled(self.f_size, Qt.KeepAspectRatio)
        revealer = self.overlay_marks(revealer)

        self.filename_prefix = 'revealer_'
        revealer.save(self.get_path_to_revealer_file('.png'))
        self.toPdf(QImage(revealer))

    def make_cypherseed(self, img, rawnoise, calibration=False, is_seed = True):
        img = img.convertToFormat(QImage.Format_Mono)
        p = QPainter()
        p.begin(img)
        p.setCompositionMode(26) #xor
        p.drawImage(0, 0, rawnoise)
        p.end()
        cypherseed = self.pixelcode_2x2(img)
        cypherseed = QBitmap.fromImage(cypherseed)
        cypherseed = cypherseed.scaled(self.f_size, Qt.KeepAspectRatio)
        cypherseed = self.overlay_marks(cypherseed, True, calibration)

        if not is_seed:
            self.filename_prefix = 'custom_secret_'
            self.was = _('Custom secret')
        else:
            self.filename_prefix = self.wallet_name + '_seed_'
            self.was = self.wallet_name + ' ' + _('seed')
            if self.extension:
                self.ext_warning(self.c_dialog)


        if not calibration:
            self.toPdf(QImage(cypherseed))
            cypherseed.save(self.get_path_to_revealer_file('.png'))
            self.bcrypt(self.c_dialog)
        return cypherseed

    def calibration(self):
        img = QImage(self.SIZE[0], self.SIZE[1], QImage.Format_Mono)
        bitmap = QBitmap.fromImage(img, Qt.MonoOnly)
        bitmap.fill(Qt.black)
        self.make_calnoise()
        img = self.overlay_marks(self.calnoise.scaledToHeight(self.f_size.height()), False, True)
        self.calibration_pdf(img)
        QDesktopServices.openUrl(QUrl.fromLocalFile(self.get_path_to_calibration_file()))
        return img

    def toPdf(self, image):
        printer = QPrinter()
        printer.setPaperSize(QSizeF(210, 297), QPrinter.Millimeter)
        printer.setResolution(600)
        printer.setOutputFormat(QPrinter.PdfFormat)
        printer.setOutputFileName(self.get_path_to_revealer_file('.pdf'))
        printer.setPageMargins(0,0,0,0,6)
        painter = QPainter()
        painter.begin(printer)

        delta_h = round(image.width()/self.abstand_v)
        delta_v = round(image.height()/self.abstand_h)

        size_h = round(2028+((int(self.calibration_h)*2028/(2028-(delta_h*2)+int(self.calibration_h)))//2))
        size_v = round(1284+((int(self.calibration_v)*1284/(1284-(delta_v*2)+int(self.calibration_v)))//2))

        image =  image.scaled(size_h, size_v)

        painter.drawImage(553,533, image)
        wpath = QPainterPath()
        wpath.addRoundedRect(QRectF(553,533, size_h, size_v), 19, 19)
        painter.setPen(QPen(Qt.black, 1))
        painter.drawPath(wpath)
        painter.end()

    def calibration_pdf(self, image):
        printer = QPrinter()
        printer.setPaperSize(QSizeF(210, 297), QPrinter.Millimeter)
        printer.setResolution(600)
        printer.setOutputFormat(QPrinter.PdfFormat)
        printer.setOutputFileName(self.get_path_to_calibration_file())
        printer.setPageMargins(0,0,0,0,6)

        painter = QPainter()
        painter.begin(printer)
        painter.drawImage(553,533, image)
        font = QFont('Source Sans 3', 10, QFont.Bold)
        painter.setFont(font)
        painter.drawText(254,277, _("Calibration sheet"))
        font = QFont('Source Sans 3', 7, QFont.Bold)
        painter.setFont(font)
        painter.drawText(600,2077, _("Instructions:"))
        font = QFont("", 7, QFont.Normal)
        painter.setFont(font)
        painter.drawText(700, 2177, _("1. Place this paper on a flat and well iluminated surface."))
        painter.drawText(700, 2277, _("2. Align your Revealer borderlines to the dashed lines on the top and left."))
        painter.drawText(700, 2377, _("3. Press slightly the Revealer against the paper and read the numbers that best "
                                      "match on the opposite sides. "))
        painter.drawText(700, 2477, _("4. Type the numbers in the software"))
        painter.end()

    def pixelcode_2x2(self, img):
        result = QImage(img.width()*2, img.height()*2, QImage.Format_ARGB32)
        white = qRgba(255,255,255,0)
        black = qRgba(0,0,0,255)

        for x in range(img.width()):
            for y in range(img.height()):
                c = img.pixel(QPoint(x,y))
                colors = QColor(c).getRgbF()
                if colors[0]:
                    result.setPixel(x*2+1,y*2+1, black)
                    result.setPixel(x*2,y*2+1, white)
                    result.setPixel(x*2+1,y*2, white)
                    result.setPixel(x*2, y*2, black)

                else:
                    result.setPixel(x*2+1,y*2+1, white)
                    result.setPixel(x*2,y*2+1, black)
                    result.setPixel(x*2+1,y*2, black)
                    result.setPixel(x*2, y*2, white)
        return result

    def overlay_marks(self, img, is_cseed=False, calibration_sheet=False):
        border_color = Qt.white
        base_img = QImage(self.f_size.width(),self.f_size.height(), QImage.Format_ARGB32)
        base_img.fill(border_color)
        img = QImage(img)

        painter = QPainter()
        painter.begin(base_img)

        total_distance_h = round(base_img.width() / self.abstand_v)
        dist_v = round(total_distance_h) // 2
        dist_h = round(total_distance_h) // 2

        img = img.scaledToWidth(base_img.width() - (2 * (total_distance_h)))
        painter.drawImage(total_distance_h,
                          total_distance_h,
                          img)

        #frame around image
        pen = QPen(Qt.black, 2)
        painter.setPen(pen)

        #horz
        painter.drawLine(0, total_distance_h, base_img.width(), total_distance_h)
        painter.drawLine(0, base_img.height()-(total_distance_h), base_img.width(), base_img.height()-(total_distance_h))
        #vert
        painter.drawLine(total_distance_h, 0,  total_distance_h, base_img.height())
        painter.drawLine(base_img.width()-(total_distance_h), 0,  base_img.width()-(total_distance_h), base_img.height())

        #border around img
        border_thick = 6
        Rpath = QPainterPath()
        Rpath.addRect(QRectF((total_distance_h)+(border_thick/2),
                             (total_distance_h)+(border_thick/2),
                             base_img.width()-((total_distance_h)*2)-((border_thick)-1),
                             (base_img.height()-((total_distance_h))*2)-((border_thick)-1)))
        pen = QPen(Qt.black, border_thick)
        pen.setJoinStyle (Qt.MiterJoin)

        painter.setPen(pen)
        painter.drawPath(Rpath)

        Bpath = QPainterPath()
        Bpath.addRect(QRectF((total_distance_h), (total_distance_h),
                             base_img.width()-((total_distance_h)*2), (base_img.height()-((total_distance_h))*2)))
        pen = QPen(Qt.black, 1)
        painter.setPen(pen)
        painter.drawPath(Bpath)

        pen = QPen(Qt.black, 1)
        painter.setPen(pen)
        painter.drawLine(0, base_img.height()//2, total_distance_h, base_img.height()//2)
        painter.drawLine(base_img.width()//2, 0, base_img.width()//2, total_distance_h)

        painter.drawLine(base_img.width()-total_distance_h, base_img.height()//2, base_img.width(), base_img.height()//2)
        painter.drawLine(base_img.width()//2, base_img.height(), base_img.width()//2, base_img.height() - total_distance_h)

        #print code
        f_size = 37
        font = QFont("DejaVu Sans Mono", f_size-11, QFont.Bold)
        font.setPixelSize(35)
        painter.setFont(font)

        if not calibration_sheet:
            if is_cseed: #its a secret
                painter.setPen(QPen(Qt.black, 1, Qt.DashDotDotLine))
                painter.drawLine(0, dist_v, base_img.width(), dist_v)
                painter.drawLine(dist_h, 0,  dist_h, base_img.height())
                painter.drawLine(0, base_img.height()-dist_v, base_img.width(), base_img.height()-(dist_v))
                painter.drawLine(base_img.width()-(dist_h), 0,  base_img.width()-(dist_h), base_img.height())

                painter.drawImage(((total_distance_h))+11, ((total_distance_h))+11,
                                  QImage(icon_path('electrumb.png')).scaledToWidth(round(2.1*total_distance_h), Qt.SmoothTransformation))

                painter.setPen(QPen(Qt.white, border_thick*8))
                painter.drawLine(int(base_img.width()-total_distance_h-(border_thick*8)/2-(border_thick/2)-2),
                                 int(base_img.height()-total_distance_h-((border_thick*8)/2)-(border_thick/2)-2),
                                 int(base_img.width()-total_distance_h-(border_thick*8)/2-(border_thick/2)-2 - 77),
                                 int(base_img.height()-total_distance_h-((border_thick*8)/2)-(border_thick/2)-2))
                painter.setPen(QColor(0,0,0,255))
                painter.drawText(QRect(0, base_img.height()-107, base_img.width()-total_distance_h - border_thick - 11,
                                       base_img.height()-total_distance_h - border_thick), Qt.AlignRight,
                                 self.versioned_seed.version + '_'+self.versioned_seed.checksum)
                painter.end()

            else: # revealer

                painter.setPen(QPen(border_color, 17))
                painter.drawLine(0, dist_v, base_img.width(), dist_v)
                painter.drawLine(dist_h, 0,  dist_h, base_img.height())
                painter.drawLine(0, base_img.height()-dist_v, base_img.width(), base_img.height()-(dist_v))
                painter.drawLine(base_img.width()-(dist_h), 0,  base_img.width()-(dist_h), base_img.height())

                painter.setPen(QPen(Qt.black, 2))
                painter.drawLine(0, dist_v, base_img.width(), dist_v)
                painter.drawLine(dist_h, 0,  dist_h, base_img.height())
                painter.drawLine(0, base_img.height()-dist_v, base_img.width(), base_img.height()-(dist_v))
                painter.drawLine(base_img.width()-(dist_h), 0,  base_img.width()-(dist_h), base_img.height())
                logo = QImage(icon_path('revealer_c.png')).scaledToWidth(round(1.3*(total_distance_h)))
                painter.drawImage(int(total_distance_h+border_thick), int(total_distance_h+border_thick), logo, Qt.SmoothTransformation)

                #frame around logo
                painter.setPen(QPen(Qt.black, border_thick))
                painter.drawLine(int(total_distance_h+border_thick), int(total_distance_h+logo.height()+3*(border_thick/2)),
                                 int(total_distance_h+logo.width()+border_thick), int(total_distance_h+logo.height()+3*(border_thick/2)))
                painter.drawLine(int(logo.width()+total_distance_h+3*(border_thick/2)), int(total_distance_h+(border_thick)),
                                 int(total_distance_h+logo.width()+3*(border_thick/2)), int(total_distance_h+logo.height()+(border_thick)))

                #frame around code/qr
                qr_size = 179

                painter.drawLine(int((base_img.width()-((total_distance_h))-(border_thick/2)-2)-qr_size),
                                 int((base_img.height()-((total_distance_h)))-((border_thick*8))-(border_thick/2)-2),
                                 int((base_img.width()//2+(total_distance_h/2)-border_thick-(border_thick*8)//2)-qr_size),
                                 int((base_img.height()-((total_distance_h)))-((border_thick*8))-(border_thick/2)-2))

                painter.drawLine(int((base_img.width()//2+(total_distance_h/2)-border_thick-(border_thick*8)//2)-qr_size),
                                 int((base_img.height()-((total_distance_h)))-((border_thick*8))-(border_thick/2)-2),
                                 int(base_img.width()//2 + (total_distance_h/2)-border_thick-(border_thick*8)//2-qr_size),
                                 int((base_img.height()-((total_distance_h)))-(border_thick/2)-2))

                painter.setPen(QPen(Qt.white, border_thick * 8))
                painter.drawLine(
                    int(base_img.width() - ((total_distance_h)) - (border_thick * 8) / 2 - (border_thick / 2) - 2),
                    int((base_img.height() - ((total_distance_h))) - ((border_thick * 8) / 2) - (border_thick / 2) - 2),
                    int(base_img.width() / 2 + (total_distance_h / 2) - border_thick - qr_size),
                    int((base_img.height() - ((total_distance_h))) - ((border_thick * 8) / 2) - (border_thick / 2) - 2))

                painter.setPen(QColor(0,0,0,255))
                painter.drawText(QRect(int(((base_img.width()/2) +21)-qr_size),
                                       int(base_img.height()-107),
                                       int(base_img.width()-total_distance_h - border_thick -93),
                                       int(base_img.height()-total_distance_h - border_thick)),
                                 Qt.AlignLeft, self.versioned_seed.get_ui_string_version_plus_seed())
                painter.drawText(QRect(0, base_img.height()-107, base_img.width()-total_distance_h - border_thick -3 -qr_size,
                                       base_img.height()-total_distance_h - border_thick), Qt.AlignRight, self.versioned_seed.checksum)

                # draw qr code
                qr_qt = self.paintQR(self.versioned_seed.get_ui_string_version_plus_seed()
                                     + self.versioned_seed.checksum)
                target = QRectF(base_img.width()-65-qr_size,
                                base_img.height()-65-qr_size,
                                qr_size, qr_size)
                painter.drawImage(target, qr_qt)
                painter.setPen(QPen(Qt.black, 4))
                painter.drawLine(
                    int(base_img.width()-65-qr_size),
                    int(base_img.height()-65-qr_size),
                    int(base_img.width() - 65 - qr_size),
                    int((base_img.height() - total_distance_h) - (border_thick * 8) - (border_thick / 2) - 4),
                )
                painter.drawLine(
                    int(base_img.width()-65-qr_size),
                    int(base_img.height()-65-qr_size),
                    int(base_img.width() - 65),
                    int(base_img.height()-65-qr_size),
                )
                painter.end()

        else: # calibration only
            painter.end()
            cal_img = QImage(self.f_size.width() + 100, self.f_size.height() + 100,
                              QImage.Format_ARGB32)
            cal_img.fill(Qt.white)

            cal_painter = QPainter()
            cal_painter.begin(cal_img)
            cal_painter.drawImage(0,0, base_img)

            #black lines in the middle of border top left only
            cal_painter.setPen(QPen(Qt.black, 1, Qt.DashDotDotLine))
            cal_painter.drawLine(0, dist_v, base_img.width(), dist_v)
            cal_painter.drawLine(dist_h, 0,  dist_h, base_img.height())

            pen = QPen(Qt.black, 2, Qt.DashDotDotLine)
            cal_painter.setPen(pen)
            n=15

            cal_painter.setFont(QFont("DejaVu Sans Mono", 21, QFont.Bold))
            for x in range(-n,n):
                #lines on bottom (vertical calibration)
                cal_painter.drawLine(int((((base_img.width())/(n*2)) *(x))+ (base_img.width()//2)-13),
                                     int(x+2+base_img.height()-(dist_v)),
                                     int((((base_img.width())/(n*2)) *(x))+ (base_img.width()//2)+13),
                                     int(x+2+base_img.height()-(dist_v)))

                num_pos = 9
                if x > 9 : num_pos = 17
                if x < 0 : num_pos = 20
                if x < -9: num_pos = 27

                cal_painter.drawText(int((((base_img.width())/(n*2)) *(x)) + (base_img.width()//2)-num_pos),
                                     int(50+base_img.height()-(dist_v)),
                                     str(x))

                #lines on the right (horizontal calibrations)

                cal_painter.drawLine(int(x+2+(base_img.width()-(dist_h))),
                                     int(((base_img.height()/(2*n)) *(x))+ (base_img.height()/n)+(base_img.height()//2)-13),
                                     int(x+2+(base_img.width()-(dist_h))),
                                     int(((base_img.height()/(2*n)) *(x))+ (base_img.height()/n)+(base_img.height()//2)+13))


                cal_painter.drawText(int(30+(base_img.width()-(dist_h))),
                                     int(((base_img.height()/(2*n)) *(x))+ (base_img.height()//2)+13),
                                     str(x))

            cal_painter.end()
            base_img = cal_img

        return base_img

    def paintQR(self, data):
        if not data:
            return
        qr = qrcode.QRCode()
        qr.add_data(data)
        matrix = qr.get_matrix()
        k = len(matrix)
        border_color = Qt.white
        base_img = QImage(k * 5, k * 5, QImage.Format_ARGB32)
        base_img.fill(border_color)
        qrpainter = QPainter()
        qrpainter.begin(base_img)
        boxsize = 5
        size = k * boxsize
        left = (base_img.width() - size)//2
        top = (base_img.height() - size)//2
        qrpainter.setBrush(Qt.black)
        qrpainter.setPen(Qt.black)

        for r in range(k):
            for c in range(k):
                if matrix[r][c]:
                    qrpainter.drawRect(left+c*boxsize, top+r*boxsize, boxsize - 1, boxsize - 1)
        qrpainter.end()
        return base_img

    def calibration_dialog(self, window):
        d = WindowModalDialog(window, _("Revealer - Printer calibration settings"))

        d.setMinimumSize(100, 200)

        vbox = QVBoxLayout(d)
        vbox.addWidget(QLabel(''.join(["<br/>", _("If you have an old printer, or want optimal precision"),"<br/>",
                                       _("print the calibration pdf and follow the instructions "), "<br/>","<br/>",
                                    ])))
        self.calibration_h = self.config.get('calibration_h')
        self.calibration_v = self.config.get('calibration_v')
        cprint = QPushButton(_("Open calibration pdf"))
        cprint.clicked.connect(self.calibration)
        vbox.addWidget(cprint)

        vbox.addWidget(QLabel(_('Calibration values:')))
        grid = QGridLayout()
        vbox.addLayout(grid)
        grid.addWidget(QLabel(_('Right side')), 0, 0)
        horizontal = QLineEdit()
        horizontal.setText(str(self.calibration_h))
        grid.addWidget(horizontal, 0, 1)

        grid.addWidget(QLabel(_('Bottom')), 1, 0)
        vertical = QLineEdit()
        vertical.setText(str(self.calibration_v))
        grid.addWidget(vertical, 1, 1)

        vbox.addStretch()
        vbox.addSpacing(13)
        vbox.addLayout(Buttons(CloseButton(d), OkButton(d)))

        if not d.exec_():
            return

        self.calibration_h = int(Decimal(horizontal.text()))
        self.config.set_key('calibration_h', self.calibration_h)
        self.calibration_v = int(Decimal(vertical.text()))
        self.config.set_key('calibration_v', self.calibration_v)


