# Copyright 3D Control Systems, Inc. All Rights Reserved 2017-2019.
# Built in San Francisco.

# This software is distributed under a commercial license for personal,
# educational, corporate or any other use.
# The software as a whole or any parts of it is prohibited for distribution or
# use without obtaining a license from 3D Control Systems, Inc.

# All software licenses are subject to the 3DPrinterOS terms of use
# (available at https://www.3dprinteros.com/terms-and-conditions/),
# and privacy policy (available at https://www.3dprinteros.com/privacy-policy/)

import copy
import json
import hashlib
import logging
import os
import secrets
import socket
import time
import re
import urllib.request, urllib.parse, urllib.error


try:
    # pylint: disable=import-error
    import pam #apt-get install python-pampy or pacman -S python-pam on ArchLinuxARM
except:
    pam = None


import rights
import paths
import platforms
import config
import joystick_interface
import log
import rights
import user_login
import version
import printer_settings_and_id


# This class exist as a compatibility hack to use old code with aiohttp
# TODO rewrite routing natively using aiohttp.web's router
class PageFormer:

    WRITE_CHUNK_SIZE = 16384
    COOKIE_TOKEN_LENGHT = 256
    COOKIE_NAME = 'token'
    COOKIE_NAME_HTTPS = 'token-https'

    @staticmethod
    def decode_url_to_unicode(url):
        return urllib.parse.unquote(url.decode('utf-8').replace('+', '%20'))

    @staticmethod
    def htmlify_text(text, ignore_endofline=False):
        text = text.replace('\t', '&emsp;')
        if not ignore_endofline:
            text = text.replace('\n', '<br />')
        return text

    def __init__(self, app, content, host, method, path, secure, remote, cookies, cookie_tokens):
        self.logger = logging.getLogger(__name__)
        self.app = app
        self.web_content_files = content
        self.host = host
        self.method = method
        self.path = path
        self.path_list = self.path.split('?')[0].split('/')
        self.HTTPS = secure
        self.remote = remote
        self.cookies = cookies
        self.cookie_tokens = cookie_tokens
        self.args = urllib.parse.parse_qs(urllib.parse.urlparse(self.path).query)
        self.status = 200
        self.headers = {}
        self.body = ""
        self.new_cookie_token = ""
        self.config = config.get_settings()
        self.cloud_url = self.config['URL']
        self.style = self.config.get("web_interface", {}).get("style", "style")
        self.show_your_account_button = not app.offline_mode
        self.wizard_enabled = False
        self.logger = logging.getLogger(__name__)

    def find_printer_interface(self, usb_info_string):
        for printer_interface in self.app.printer_interfaces:
            if printer_interface.id_string == usb_info_string:
                return printer_interface

    def write_with_autoreplace(self, page, response=200, headers = {}):
        version_string = "v.%s.%s_%s" % (version.version, version.build, version.branch)
        is_text_page = type(page) == str
        if is_text_page: #binary data like images will have type bytes and doesn't need patching
            page = page.replace("!!!VERSION!!!", version_string)
            page = page.replace("!!!URL!!!", self.cloud_url.replace("cli-", "", 1))
            page = page.replace("/style.css", f"/{self.style}.css")
            if self.app.restart_flag:
                page = page.replace('Quit', 'Restart')
        if self.app.stop_flag:
            headers["Connection"] = "close"
        else:
            headers["Connection"] = "keep-alive"
        if is_text_page: # vital for correct length calculation
            length = len(page.encode("utf-8"))
        else:
            length = len(page)
        headers["Content-Length"] = str(length)
        if not "Content-Type" in headers:
            headers["Content-Type"] = "text/html; charset=utf-8"
        if is_text_page:
            page = page.encode('utf-8')
        self.status = response
        self.headers = headers
        self.body = page

    def parse_post(self, *value_names):
        posts = []
        if hasattr(self, "post_data"):
            for key in value_names:
                posts.append(self.post_data.get(key, ""))
            if len(value_names) == 1:
                return posts[0]
            if len(posts) < len(value_names):
                posts.extend([''] * (len(value_names) - len(posts)))
            self.logger.debug("Post parsed as: %s" % posts)
        else:
            parse_obj = urllib.parse.urlparse(self.path)
            args = urllib.parse.parse_qs(parse_obj.query)
            for key in value_names:
                posts = args.get(key, [None])[0]
        return posts

    def get_cookie(self, key):
        self.cookies.get(key)

    def get_cookie_name(self):
        if self.HTTPS:
            return self.COOKIE_NAME_HTTPS
        else:
            return self.COOKIE_NAME

    def write_message(self, message, show_time=2, response=200, headers={},\
            redirect="/", button_text="", button_url="/", button_method="get", back_button_to="", return_page=False):
        page = self.web_content_files['templates']['pages']['message.html']
        page = page.replace('!!!MESSAGE!!!', message)
        if show_time:
            page = page.replace('!!!SHOW_TIME!!!', str(show_time))
        else:
            page = page.replace('<meta http-equiv="refresh" content="!!!SHOW_TIME!!!; url=/" />', '')
        page = page.replace('url=/', 'url=' + redirect)
        button_html = ""
        if button_text:
            button_html = self.web_content_files['templates']['buttons']['generic_button.html']
            button_html = button_html.replace("%VALUE%", button_text)
            button_html = button_html.replace("%PATH%", button_url)
            button_html = button_html.replace("%METHOD%", button_method)
            button_html = button_html.replace("%CLASS%", "big_button")
        if back_button_to:
            back_html = self.form_back_button(back_button_to)
        else:
            back_html = ""
        page = page.replace("!!!BACKBUT!!!", back_html)
        page = page.replace("!!!BUTTON!!!", button_html)
        if not return_page:
            self.write_with_autoreplace(page, response=response, headers=headers)
        else:
            return page

    def path_contains(self, path_component):
        # self.logger.debug(f"Path contains check {path_component} in {self.path_list}")
        return path_component in self.path_list

    def need_remote_login(self):
        #self.logger.debug(f'Remote: {self.remote}\nHost: {self.host}')
        try:
            host = self.host.split(':')[0]
        except:
            host = "127.0.0.1"
        try:
            #without conversions below an error like '127.0.0.1' != 'localhost' is possible
            host_addr = socket.gethostbyname(host)
            remote_addr = socket.gethostbyname(self.remote)
        except:
            # sometimes gethostbyname is failing with UnicodeError or socket.error
            # that is a fallback to old way of comparing host and remote
            host_addr = host
            remote_addr = self.remote
        if remote_addr != host_addr:
            token = self.cookies.get(self.get_cookie_name())
            return token not in self.cookie_tokens

    def handle_wizard(self):
        if len(self.path_list) < 3 or not self.path_list[2]:
            page = self.web_content_files['pages']['wizard']['greetings.html']
        else:
            page = self.web_content_files['pages']['wizard'].get(self.path_list[2] + '.html')
        if page:
            self.write_with_autoreplace(page)
        else:
            self.write_message(f'Error: page {self.path} not found', 0, 404, button_text='Got it')

    def process_api(self):
        if 'user_login' in self.path:
            self.process_login()
        elif 'logout' in self.path:
            self.process_logout()
        elif 'kill_conveyor' in self.path:
            self.kill_conveyor()
        elif 'check_makerware' in self.path:
            self.check_makerware()
        elif 'check_if_printing' in self.path:
            self.check_if_printing()
        elif 'check_host' in self.path:
            self.check_host()

    @log.log_exception(exit_on_error=False)
    def do_GET(self):
        if self.need_remote_login():
            self.write_with_autoreplace(self.web_content_files['pages']['auth.html'], response=401)
        elif self.path_contains('api'):
            self.process_api()
        elif self.path_contains('ajax'):
            self.ajax_do_GET()
        elif self.path_contains('user_login') or "user_login=" in self.path:
            self.process_login()
        elif self.path_contains('logout'):
            self.process_logout()
        elif self.path_contains('quit'):
            self.quit_main_app()
        elif self.path_contains('show_logs'):
            self.show_logs()
        elif self.path_contains('show_release_notes'):
            with open(paths.RELEASE_NOTES_FILE_PATH) as f:
                self.body = f.read()
        elif self.path_contains('clear_logs'):
            self.clear_logs()
        elif self.path_contains('open_logs'):
            log.open_settings_folder()
            self.write_message(self.form_back_button('/'), show_time = 0)
        elif self.path_contains('kill_conveyor') or 'kill_conveyor=' in self.path:
            self.kill_conveyor()
        elif self.path_contains('connect_to_network_printer'):
            self.write_with_autoreplace(self.form_connect_to_network_printer_page())
        elif self.path_contains('detect_network_clients'):
            self.form_detect_network_clients_page()
        elif self.path_contains('edit_settings'):
            self.write_with_autoreplace(self.form_edit_settings_page())
        elif self.path_contains('update_from_file'):
            if self.config.get('forbid_update_from_file'):
                self.write_message('Any file uploads are forbidden in this branch of software.', show_time=0, button_text='Back')
            else:
                self.write_with_autoreplace(self.web_content_files['templates']['pages']['update_from_file.html'])
        elif self.path_contains('manage_settings'):
            self.write_with_autoreplace(self.form_manage_settings_page())
        elif self.path_contains('config') and self.path_contains('get'):
            self.write_with_autoreplace(self.get_config_value(), headers={'Content-Type':'application/json'})
        elif self.path_contains('manage_ssh_keys'):
            if self.check_if_rpi():
                self.write_with_autoreplace(self.web_content_files['templates']['pages']['rpi_access_control.html'])
            else:
                self.write_message('Only available for RaspberryPi')
        elif self.path_contains('change_password'):
            if self.check_if_rpi():
                self.form_change_ssh_password()
            else:
                self.write_message('Only available for RaspberryPi')
        elif self.path_contains('plugins_page'):
            self.write_with_autoreplace(self.web_content_files['templates']['pages']['plugins_page.html'])
        elif self.path_contains('check_host') or 'check_host' in self.path:
            self.check_host()
        elif self.path_contains('check_makerware') or 'check_makerware' in self.path:
            self.check_makerware()
        elif self.path_contains('check_linux_rights'):
            self.check_linux_rights()
        elif self.path_contains('check_if_printing') or 'check_if_printing' in self.path:
            self.check_if_printing()
        elif self.path_contains('network_printer_list_page'):
            self.write_with_autoreplace(self.web_content_files['templates']['pages']['network_printer_list.html'])
        elif self.path_contains('network_printers_list'):
            self.network_printers_list()
        elif self.path_contains('plugins_list'):
            self.plugins_list()
        elif self.path_contains('install_plugin_page'):
            self.write_with_autoreplace(self.web_content_files['templates']['pages']['install_plugin.html'])
        elif self.path_contains('upload_to_storage_page'):
            if self.config.get('forbid_update_from_file'):
                self.write_message('Any file uploads are forbidden in this branch of software.', show_time=0, button_text='Back')
            else:
                self.upload_to_storage_page()
        elif self.path_contains('enter_offline_mode'):
            self.app.set_offline_mode(True)
            self.write_message('Entering offline mode...', show_time=1)
        elif self.path_contains('exit_offline_mode'):
            self.app.set_offline_mode(False)
            self.write_message('Exiting offline mode...', show_time=1)
        elif self.path_contains('reload_html'):
            self.app.web_interface.reload_web_content()
            self.write_message("OK", show_time=0.2)
        elif self.path_contains('detection_wizard_page'):
            self.get_detection_wizard_page()
        elif self.path_contains('detection_wizard_start'):
            self.detection_wizard_start()
        elif self.path_contains('compat_temp'):
            self.write_with_autoreplace(self.get_compat_template())
        elif self.path_contains('favicon.ico'):
            self.answer_with_image('favicon.ico')
        elif self.path_contains('printers_ids'):
            self.printers_ids()
        elif self.path_contains('printer_by_id'):
            self.printer_by_id()
        elif self.path_contains('edit_network'):
            self.form_edit_network()
        elif self.path.startswith('/api/json/status'):
            self.json_printers_status()
        elif self.path_contains('confirm_logout') or 'logout=' in self.path:
            self.process_logout()
        elif self.path_contains('wizard'):
            self.handle_wizard()
        else:
            page = self.form_main_page()
            if page:
                self.write_with_autoreplace(page, headers={'Access-Control-Allow-Origin':'*'})
            else:
                self.write_message('Initializing...', show_time=1, headers={'Access-Control-Allow-Origin':'*'})

    def ajax_do_GET(self):
        if self.path_contains('write_log_tail'):
            self.write_log_tail()
        elif self.path_contains('get_closing_client_info'):
            self.write_with_autoreplace(self.form_closing_message(), headers={'Content-Type': 'text/html'})
        elif self.path_contains('check_login'):
            login = bool(hasattr(self.app, "user_login") and self.app.user_login.login) or self.app.offline_mode
            response = json.dumps({'result': login})
            self.write_with_autoreplace(response, headers={'Content-Type':'application/json'})
        elif self.path_contains('manage_printer'):
            self.manage_printer()
        elif self.path_contains('is_offline'):
            response = json.dumps({'result': self.app.offline_mode})
            self.write_with_autoreplace(response, headers={'Content-Type':'application/json'})

    @log.log_exception(exit_on_error=False)
    def do_POST(self, post_data):
        self.post_data = post_data
        if self.path_contains('remote_login'):
            self.process_remote_login()
        elif self.need_remote_login():
            self.write_with_autoreplace(self.web_content_files['pages']['auth.html'], response=401)
        elif self.path_contains('api'):
            self.process_api()
        elif self.path_contains('ajax'):
            self.ajax_do_POST()
        elif self.path_contains('joystick'):
            self.joystick_do_POST()
        elif self.path_contains('login'):
            self.process_login()
        elif self.path_contains('close_wizard'):
            self.close_wizard()
        elif self.path_contains('confirm_quit'):
            self.quit_main_app()
        elif self.path_contains('quit'):
            self.quit_main_app()
        elif self.path_contains('logout_confirmation'):
            self.write_with_autoreplace(self.form_logout_confirm_page())
        elif self.path_contains('confirm_logout'):
            self.process_logout()
        elif self.path_contains('kill_conveyor') or 'kill_conveyor=' in self.path:
            self.kill_conveyor()
        elif self.path_contains('add_user_groups'):
            self.add_user_groups()
        elif self.path_contains('ignore_groups_warning'):
            self.ignore_groups_warning()
        elif self.path_contains('get_updates'):
            self.get_updates()
        elif self.path_contains('update_software'):
            self.update_software()
        elif self.path_contains('wizard'):
            self.handle_wizard()
        elif self.path_contains('quit_confirmation'):
            self.write_with_autoreplace(self.form_quit_confirm_page())
        elif self.path_contains('choose_camera'):
            self.choose_camera()
        elif self.path_contains('switch_camera'):
            self.switch_camera()
        elif self.path_contains('choose_printer_type'):
            self.choose_printer_type()
        elif self.path_contains('confirm_printer_groups'):
            self.confirm_printer_groups()
        elif self.path_contains('report_problem'):
            self.report_problem()
        elif self.path_contains('send_problem_report'):
            self.send_problem_report()
        elif self.path_contains('restart_camera'):
            self.app.camera_controller.restart_camera()
            self.write_message('Camera module restarted', redirect="/manage_settings")
        elif self.path_contains('set_new_printer_name'):
            self.set_new_printer_name()
        elif self.path_contains('toggle_virtual_printer'):
            self.toggle_virtual_printer()
        elif self.path_contains('toggle_gpio'):
            self.toggle_gpio()
        elif self.path_contains('toggle_prt_in_dual_serial'):
            self.toggle_prt_in_dual_serial()
        elif self.path_contains('toggle_clisma'):
            self.toggle_clisma()
        elif self.path_contains('toggle_verbose'):
            self.toggle_verbose()
        elif self.path_contains('intercept_pause'):
            self.toggle_intercept_pause()
        elif self.path_contains('reset_printer_type'):
            self.reset_printer_type()
        elif self.path_contains('restore_default_settings'):
            self.write_with_autoreplace(self.web_content_files['templates']['pages']['restore_default_settings.html'])
        elif self.path_contains('confirm_default_settings_restoring'):
            config.Config.instance().restore_default_settings()
            self.write_message('Default settings restored. Please restart app to apply changes.')
        elif self.path_contains('process_network_printer'):
            self.process_network_printer()
        elif self.path_contains('confirm_detect_network_printers'):
            self.write_with_autoreplace(self.web_content_files['templates']['pages']
                                        ['confirm_detect_printers.html'])
        elif self.path_contains('auto_detect_network_printers'):
            network_printers_detector = self.app.detectors.get('NetworkDetector')
            if network_printers_detector:
                network_printers_detector.detect()
            self.write_message('<p>Detecting network printers...</p>'
                               'If you have a printer connected to your network<br />it will appear in printers list',
                               show_time=4)
        elif self.path_contains('process_pairing_network_printer'):
            self.do_GET()
        elif self.path_contains('forget_network_printer'):
            self.forget_network_printer()
        elif self.path_contains('save_settings'):
            self.save_settings()
        elif self.path_contains('settings'):
            self.settings_do_POST()
        elif self.path_contains('accept_update_from_file'):
            self.accept_update_from_file()
        elif self.path_contains('control_plugin'):
            self.control_plugin()
        elif self.path_contains('upload_plugin'):
            self.accept_and_install_plugin_file()
        elif self.path_contains('change_ssh_password'):
            self.change_ssh_password()
        elif self.path_contains('generate_ssh_keys'):
            self.generate_ssh_keys()
        elif self.path_contains('revoke_all_ssh_keys'):
            self.revoke_all_ssh_keys()
        elif self.path_contains('add_public_key'):
            self.add_public_key()
        elif self.path_contains('upload_to_storage'):
            self.upload_to_storage()
        elif self.path_contains('local_file_print_or_delete'):
            self.local_file_print_or_delete()
        elif self.path_contains('select_printer_groups'):
            self.select_printer_groups()
        elif self.path_contains('print_from_file_or_delete'):
            self.print_from_file_or_delete()
        elif self.path_contains('detection_wizard_action'):
            self.detection_wizard_action()
        elif self.path_contains('detection_wizard_form_action'):
            self.detection_wizard_form_action()
        elif self.path_contains('detection_wizard_start'):
            self.detection_wizard_start()
        elif self.path_contains('upload_to_storage_page'):
            self.upload_to_storage_page()
        elif self.path_contains('edit_network'):
            self.edit_network()
        elif self.path_contains('enable_uart'):
            self.enable_uart()
        elif self.path_contains('disable_uart'):
            self.disable_uart()
        elif self.path_contains('uart_confirmation_page'):
            self.uart_confirmation_page()
        # elif self.path_contains('printer_cloud_binding_page'):
        #     self.show_printer_cloud_binding_page()
        elif self.path_contains('printer_conn_type_page'):
            self.show_printer_conn_type_page()
        # elif self.path_contains('change_cloud_printer_id'):
        #     self.change_cloud_printer_id()
        elif self.path_contains('select_printer_conn_type'):
            self.select_printer_conn_type()
        else:
            self.write_message('Not found', 0, 404)

    def settings_do_POST(self):
        if self.path_contains('toggle_update'):
            self.config['update']['enabled'] = not self.config['update']['enabled']
            config.Config.instance().save_settings(self.config)
            self.write_with_autoreplace(self.form_manage_settings_page())
        elif self.path_contains('toggle_auto_update'):
            self.config['update']['auto_update_enabled'] = not self.config['update']['auto_update_enabled']
            config.Config.instance().save_settings(self.config)
            self.write_with_autoreplace(self.form_manage_settings_page())
        elif self.path_contains('set_update_check_pause'):
            try:
                check_pause = int(self.post_data.get('check_pause'))
            except:
                self.write_message('Setting update check pause failed: wrong value!')
            else:
                self.config['update']['check_pause'] = int(check_pause)
                config.Config.instance().save_settings(self.config)
                self.write_with_autoreplace(self.form_manage_settings_page())

    def ajax_do_POST(self):
        if self.path_contains('rename_printer'):
            self.rename_printer()
        elif self.path_contains('select_printer_groups'):
            self.select_printer_groups()
        elif self.path_contains('select_printer_type'):
            self.select_printer_type()
        elif self.path_contains('manage_printer'):
            self.manage_printer()
        elif self.path_contains('request_printer_type_reset'):
            self.request_printer_type_reset()
        elif self.path_contains('toggle_local_mode'):
            self.toggle_local_mode()
        elif self.path_contains('cancel_printing'):
            self.cancel_print()
        else:
            self.write_message('Not found', 0, 404)

    def toggle_virtual_printer(self):
        self.app.toggle_virtual_printer()
        self.write_with_autoreplace(self.form_manage_settings_page())

    def toggle_gpio(self):
        if self.app.gpio_interface:
            self.app.gpio_interface.toggle()
            self.app.gpio_interface = None
            self.write_with_autoreplace(self.form_manage_settings_page())
        else:
            if config.get_settings()['gpio_uart']['enabled'] and not config.get_settings()['gpio_uart']['allow_use_with_buttons']:
                self.write_message('Disable GPIO UART and reboot before enabling GPIO buttons', back_button_to='/manage_settings', show_time=0)
            else:
                settings = config.get_settings()
                settings['gpio']['enabled'] = True
                config.Config.instance().save_settings(settings)
                pins_dict = settings['gpio']['buttons']
                self.write_message(f'GPIO buttons will be enabled after a reboot. Pins: {pins_dict}', back_button_to="/manage_settings", show_time=0) #TODO improve this

    def toggle_prt_in_dual_serial(self):
        self.config['backward_compatible_serials'] = not self.config['backward_compatible_serials']
        config.Config.instance().save_settings(self.config)
        self.write_with_autoreplace(self.form_manage_settings_page())

    def toggle_clisma(self):
        rights.toggle_clisma()
        self.write_with_autoreplace(self.form_manage_settings_page())

    def toggle_verbose(self):
        verbose_enabled = not self.config['verbose']
        self.config['verbose'] = verbose_enabled
        config.Config.instance().save_settings(self.config)
        for pi in self.app.printer_interfaces:
            pi.set_verbose(verbose_enabled)
        self.write_with_autoreplace(self.form_manage_settings_page())

    def toggle_intercept_pause(self):
        intercept_pause_enabled = not self.config['intercept_pause']
        self.config['intercept_pause'] = intercept_pause_enabled
        config.Config.instance().save_settings(self.config)
        self.write_with_autoreplace(self.form_manage_settings_page())

    def rename_printer(self):
        usb_info = self.post_data.get('usb_info')
        page = self.form_rename_printer_page(usb_info)
        if page:
            self.write_with_autoreplace(page)
        else:
            self.write_message('Printer not found. Probably it was disconnected.')

    def set_new_printer_name(self):
        printer_name = self.post_data.get('printer_name')
        usb_info = self.post_data.get('usb_info')
        if usb_info:
            printer_interface = self.find_printer_interface(usb_info)
            if printer_interface:
                if printer_name:
                    printer_interface.request_printer_rename(printer_name)
                    self.write_message('Printer rename requested: "' + printer_name +'"')
                else:
                    self.write_message('Wrong printer name')
        else:
            self.write_message('Printer not found. Probably it was disconnected.')

    def select_printer_groups(self):
        printer_id = self.post_data.get('printer_id')
        page = self.form_printer_groups_page(printer_id)
        if page:
            self.write_with_autoreplace(page)
        else:
            if self.config['protocol']['encryption']:
                prefix = 'https://'
            else:
                prefix = 'http://'
            back_button = self.form_back_button('/')
            self.write_message("<p>To create a new Workgroup Access code, please check Admin tab \
                                at <a href='" + prefix + \
                               "!!!URL!!!/workgroup' target='_blank'>!!!URL!!!/workgroup</a></p>\
                                Workgroup access codes is a premium \
                                feature, to use that please contact our sales: \
                                <a href='mailto:info@3dprinteros.com'>info@3dprinteros.com</a>" \
                               + back_button , show_time=0)

    def confirm_printer_groups(self):
        usb_info = self.get_unicode_string_from_current_path(b'/confirm_printer_groups/')
        printer_interface = self.find_printer_interface(usb_info)
        if printer_interface:
            printer_groups = printer_interface.get_groups()
            check_list = []
            for index in range(0, len(printer_groups)):
                check_list.append('selected_group_id_%d' % index)
            selected_groups_id = self.parse_post(*check_list)
            if type(selected_groups_id) != list:
                selected_groups_id = [] if not selected_groups_id else [str(selected_groups_id)]
            for index, group in enumerate(printer_groups):
                printer_groups[index]['active'] = str(group['id']) in selected_groups_id
            printer_interface.request_printer_groups_selection(printer_groups)
            self.write_message('Groups for ' + getattr(printer_interface, "printer_profile", {}).get('name', 'unnamed printer') + ' successfully updated')
        else:
            self.write_message('Printer not found. Probably it was disconnected.')

    def select_printer_type(self):
        usb_info = self.parse_post('usb_info')
        page = self.form_type_select_page(usb_info)
        if page:
            self.write_with_autoreplace(page)
        else:
            self.write_message('Error while displaying printer types')

    def choose_printer_type(self):
        printer_type_name, usb_info = self.parse_post('printer_type', 'usb_info')
        printer_interface = self.find_printer_interface(usb_info)
        if printer_interface:
            if printer_type_name:
                for possible_type in getattr(printer_interface, "possible_printer_types", []):
                    if possible_type['name'] == printer_type_name:
                        printer_interface.request_printer_type_selection(possible_type['alias'])
                        message = 'Printer type selected:  ' + str(printer_type_name)
                        break
                else:
                    message = 'Error: printer type is not compatible'
        else:
            message = 'Printer not found. Probably it was disconnected.'
        self.write_message(message)

    def request_printer_type_reset(self):
        usb_info = self.parse_post('usb_info')
        pi = self.find_printer_interface(usb_info)
        if not pi or pi.forced_state in ('error', 'connecting') or not pi.sender or not pi.sender.is_operational():
            self.reset_printer_type()
        else:
            page = self.form_type_reset_page(usb_info)
            if page:
                self.write_with_autoreplace(page)
            else:
                self.write_message('Printer not found. Probably it was disconnected.')

    def get_unicode_string_from_current_path(self, substring_to_remove=b''):
        path = self.path.encode('utf-8').replace(substring_to_remove, b'')
        return self.decode_url_to_unicode(path)

    def accept_update_from_file(self):
        error = self.parse_and_save_multipart_file_upload(path=paths.UPDATE_FILE_PATH)
        if error:
            message = 'Failed to accept update package!\n' + error
            self.logger.warning(message)
            self.write_message(message, show_time=0, button_text='Back')
        else:
            self.write_with_autoreplace(self.web_content_files['templates']['pages']
                                        ['update_from_file_accepted.html'])

    def process_remote_login(self):
        try:
            login, password = self.parse_post('login', 'password')
        except:
            self.write_message('Authorization failed! Wrong email or password.')
        else:
            if login and password and self.check_login_and_password(login, password):
                token = self.create_cookie_token()
                header_value = '%s=%s' % (self.get_cookie_name(), token)
                #  header_value = '%s=%s; HttpOnly' % (self.get_cookie_name(), token)
                #  if self.HTTPS:
                #      header_value += '; Secure'
                self.write_message('Login successful!<br><br>Processing...', headers= {'Set-cookie': header_value, 'URL': '/pages/main'}, response=302)
            else:
                self.write_message('Authorization failed! Wrong email or password.')

    def save_settings(self):
        settings = self.parse_post('settings')
        try:
            settings = json.loads(settings)
            config.Config.instance().save_settings(settings)
        except Exception as e:
            self.write_message('Error while saving settings<br>%s' % str(e))
        else:
            self.write_message('Settings successfully saved.<br>But will be applied only on after restart!', show_time=5)

    def joystick_do_POST(self):
        if self.path_contains('start'):
            self.start_joystick(self.parse_post('printer_id'))
        elif self.path_contains('send_command'):
            printer_id, command, value = self.parse_post('printer_id', 'command', 'value')
            joystick_interface.execute_command(self.find_printer_interface(printer_id), command, value)
            self.write_with_autoreplace('OK')
        elif self.path_contains('get_printer_info'):
            printer_id = self.parse_post('printer_id')
            pi = self.find_printer_interface(printer_id)
            if pi:
                try:
                    pi.enable_local_mode()
                except (TypeError, AttributeError):
                    self.logger.warning('No printer interface to enable local mode')
            self.send_printer_info(printer_id)
        elif self.path_contains('send_gcode'):
            printer_id, gcode_string = self.parse_post('printer_id', 'gcode_string')
            joystick_interface.send_gcode(self.find_printer_interface(printer_id), gcode_string)
            self.write_with_autoreplace('OK')

    def send_printer_info(self, printer_id):
        printer_interface = self.find_printer_interface(printer_id)
        self.write_with_autoreplace(
                json.dumps(joystick_interface.get_printers_info(printer_interface)),
                headers={'Content-Type': 'application/json'})

    def start_joystick(self, printer_id):
        printer_interface = self.find_printer_interface(printer_id)
        if printer_interface and printer_interface.sender:
            joystick_interface.init_callback(self.find_printer_interface(printer_id))
            self.write_with_autoreplace(self.form_joystick_page(printer_id))
        else:
            self.write_message('Printer not found. Probably it was disconnected.')

    def process_network_printer(self):
        printer_ip = self.post_data.get('printer_ip')
        printer_port = self.post_data.get('printer_port')
        printer_type = self.post_data.get('printer_type')
        run_detector = self.post_data.get('run_detector', False)
        password = self.post_data.get('password')
        if password == '':
            password = None
        ssh_password = self.post_data.get('ssh_password')
        if ssh_password == '':
            ssh_password = None
        serial_number = self.post_data.get('serial_number', self.post_data.get('forced_snr', self.post_data.get('forced_SNR')))
        if not serial_number and run_detector == 'on':
            run_detector = True
        else:
            run_detector = False
        try:
            printer_port = int(printer_port)
        except (TypeError, ValueError):
            printer_port = None
        if printer_ip and printer_type:
            network_detector = self.app.detectors.get('NetworkDetector')
            if network_detector:
                network_detector.remember_printer(printer_type, printer_ip, printer_port, None, None, serial_number, password, ssh_password, run_detector)
                self.write_message(f'New network printer added: {printer_ip}')
            else:
                self.write_message(f'Error. Network detector is disabled')
            self.write_message(f'New network printer added: {printer_ip}')
            return
        else:
            self.write_message('Printer IP and type must not be empty!')

    def toggle_local_mode(self):
        printer_interface = self.find_printer_interface(self.parse_post('usb_info'))
        if printer_interface:
            if printer_interface.local_mode:
                self.logger.info("Local mode off")
                printer_interface.turn_off_local_mode()
            else:
                self.logger.info("Local mode on")
                printer_interface.turn_on_local_mode()
        else:
            self.write_message('Printer not found. Probably it was disconnected.')

    def cancel_print(self):
        printer_id = self.parse_post('usb_info')
        if printer_id:
            try:
                printer_interface = self.find_printer_interface(printer_id)
                printer_interface.cancel_locally()
            except:
                self.logger.debug("Fail to cancel print job for %s" % printer_id)

    def manage_printer(self):
        usb_info = self.parse_post('usb_info')
        page = self.form_manage_printer_page(usb_info)
        if page:
            self.write_with_autoreplace(page)
        else:
            self.write_message('Error while displaying printer managing page')


    def reset_printer_type(self):
        usb_info = self.parse_post('usb_info')
        printer_interface = self.find_printer_interface(usb_info)
        if printer_interface:
            printer_interface.request_reset_printer_type()
            if printer_interface.offline_mode:
                self.write_message('Printer type reset', show_time=1)
            else:
                self.write_message('Sending printer type reset request...')
        else:
            self.write_message('Printer not found. Probably it was disconnected.')

    def choose_camera(self):
        page = self.form_choose_cam_page()
        if page:
            self.write_with_autoreplace(page)
        else:
            self.write_message('Live view feature disabled')

    def switch_camera(self):
        module_name = str(self.parse_post('module'))
        if module_name:
            self.app.camera_controller.switch_camera( \
                    new_camera_name=module_name, token=self.app.user_login.user_token)
            message = 'Live view mode switched to ' + module_name
        else:
            message = 'Live view mode not chosen'
        if self.config.get('wizard_on_start', False):
            redirect = 'wizard/choose_live_view_mode'
        else:
            redirect = "/manage_settings"
        self.write_message(message, redirect=redirect)

    def get_updates(self):
        self.write_with_autoreplace(self.form_get_updates_page())

    def update_software(self):
        error = self.app.updater.download_update()
        if error:
            self.write_message(error)
        else:
            self.write_with_autoreplace(self.form_update_downloaded_page())

    def show_logs(self):
        self.write_with_autoreplace(self.form_show_logs_page())

    def write_log_tail(self):
        parse_obj = urllib.parse.urlparse(self.path)
        args = urllib.parse.parse_qs(parse_obj.query)
        printer_id = args.get('printer_id', [None])[0]
        if not printer_id:
            printer_id = self.parse_post('usb_info')
        if not printer_id:
            logs = log.get_file_tail(log.MAIN_LOG_NAME)
        else:
            logs = log.get_file_tail(log.get_printer_log_file_path(printer_id))
        if logs:
            content = '<br />'.join(logs) + '<br />'
        else:
            content = 'No logs'
        self.write_with_autoreplace(content)

    def ignore_groups_warning(self):
        self.app.rights_checker.ignore_flag = True
        time.sleep(self.app.rights_checker.CHECK_PERIOD)
        self.do_GET()

    def add_user_groups(self):
        self.app.rights_checker.add_user_groups()
        self.quit_main_app()

    def kill_conveyor(self):
        self.app.conveyor_kill_waiter.kill()
        if not self.app.conveyor_kill_waiter.waiting:
            message = 'Conveyor was successfully stopped.<br /><br />Returning...'
        else:
            message = '3DPrinterOS was unable to stop MakerBot conveyor service..'
        self.write_message(message)

    def report_problem(self):
        self.write_with_autoreplace(self.form_report_problem_page())

    def send_problem_report(self):
        summary, description = self.parse_post('problem_summary', 'problem_description')
        report_text = 'Summary:\n' + summary + '\n\nDescription:\n' + description
        error = log.report_problem(report_text)
        if error:
            self.write_message('Reporting problem failed!<br />' + error)
        else:
            self.write_message('Thank you! Your problem was successfully reported.')

    def clear_logs(self):
        log.clear_logs()
        self.write_message('Logs successfully cleared. Writing logs stopped. \
                           <br /><br />Please restart 3DPrinterOS Client to start writing new logs!'
                           + self.form_back_button('/'), show_time=1)

    def quit_main_app(self):
        self.write_with_autoreplace(self.form_closing_page())
        self.app.stop_flag = True

    def process_login(self):
        login_object = self.app.user_login
        force_normal_login = False
        error = None
        if login_object.login_mode == login_object.APIPRINTER_MODE:
            error = 'Application is in APIPrinter mode. Login as user is impossible.'
        elif login_object.user_token:
            if login_object.login == self.parse_login()[0]:
                self.answer_on_login(True, 'You are already logged in')
                return
            else:
                error = 'Please logout first before login as other user'
        else:
            if not login_object.got_network_connection:
                error = "No network connection"
            else:
                login, password, token, force_normal_login = self.parse_login()
                if (login and password != None) or token:
                    error = login_object.login_as_user(login, password, token)
                    if type(error) == tuple:
                        error = error[len(error)-1]
                else:
                    error = "Login and password should not be empty"
        if error:
            self.answer_on_login(False, "Login error: " + error)
        else:
            if self.method == 'GET' and not force_normal_login:
                self.show_your_account_button = False
                self.config['camera']['enabled'] = False
                self.config['web_interface']['browser_opening_on_start'] = False
                self.config['wizard_on_start'] = False
            self.answer_on_login(True, 'Login successful!<br><br>Processing...')

    def parse_login(self):
        login, password, token = None, None, None
        force_normal_login = False
        if self.method == 'POST':
            login_and_password = self.parse_post('login', 'password')
            if login_and_password and len(login_and_password) == 2:
                login, password = login_and_password
                login = login.replace(' ', '+') # a hack to fix + in email. FIXME right
                password = hashlib.sha256(password.encode('utf-8')).hexdigest()
        elif self.method == 'GET':
            query = urllib.parse.urlparse(self.path).query
            args = urllib.parse.parse_qs(query)
            try:
                login = args['user_login'][0]
                if 'auth_token' in args:
                    token = args['auth_token'][0]
                elif 'password_hash' in args:
                    password = args['password_hash'][0]
                elif 'password' in args:
                    password = args['password'][0]
                    password = hashlib.sha256(password.encode('utf-8')).hexdigest()
                force_normal_login = "force_normal_login" in args
            except Exception as e:
                self.logger.error('Unable to parse login: %s' % e)
        return login, password, token, force_normal_login

    def need_image_response(self):
        query = urllib.parse.urlparse(self.path).query
        args = urllib.parse.parse_qs(query)
        return 'image_answer' in args

    def answer_on_login(self, success, message):
        if self.need_image_response():
            if success:
                self.answer_with_image('success.jpg')
            else:
                self.answer_with_image('fail.jpg')
        else:
            self.write_message(message)

    def check_host(self):
        query = urllib.parse.urlparse(self.path).query
        args = urllib.parse.parse_qs(query)
        host_id = args.get('check_host')[0]
        if host_id.lower() == self.app.host_id.lower():
            self.answer_with_image('success.jpg')
        else:
            self.answer_with_image('fail.jpg')

    def check_makerware(self):
        if self.app.conveyor_kill_waiter.waiting:
            self.answer_with_image('success.jpg')
        else:
            self.answer_with_image('fail.jpg')

    def check_if_printing(self):
        for pi in self.app.printer_interfaces:
            if pi.get_printer_state() in ('printing', 'paused', 'downloading'):
                self.answer_with_image('success.jpg')
                return
        self.answer_with_image('fail.jpg')

    def check_linux_rights(self):
        if self.app.rights_checker.waiting:
            self.answer_with_image('success.jpg')
        else:
            self.answer_with_image('fail.jpg')

    def answer_with_image(self, img_filename):
        #this function performs hack that necessary for sending requests from HTTPS to HTTP
        #answering with image is the only way to communicate between HTTPS and HTTP
        message = self.web_content_files['static']['images'][img_filename]
        if img_filename.endswith('png'):
            mime = 'image/png'
        elif img_filename.endswith('ico'):
            mime = 'image/ico'
        else:
            mime = 'image/jpeg'
        self.write_with_autoreplace(message, headers = { 'Content-Type': mime })

    def check_login_and_password(self, login, password):
        # check login using RPi's root credentials
        if platforms.get_platform() == 'rpi' and pam:
            try:
                result = pam.authenticate(login, password, service='sudo') #service sudo is essential for this to work on RPi
                if result:
                    return result
            except:
                self.logger.exception("PAM authentication error")
        # check 3dprinteros credentials login
        if not self.app.user_login.auth_tokens:
            password = hashlib.sha256(password.encode('utf-8')).hexdigest()
            stored_login, stored_password = user_login.UserLogin.load_login()
            if stored_login:
                return login == stored_login and password == stored_password
        return False

    def create_cookie_token(self):
        #  login = self.app.user_login.login
        #  if login:
        #      md5hash = hashlib.md5()
        #      md5hash.update(login.encode("utf-8"))
        #      md5hash.update(self.app.user_login.user_token.encode("utf-8"))
        #      md5hash.update(time.strftime('%Y%m%d').encode("utf-8"))
        #      token = md5hash.hexdigest()
        token = secrets.token_hex(self.COOKIE_TOKEN_LENGHT)
        self.cookie_tokens.append(token)
        self.new_cookie_token = token
        return token

    def check_if_rpi(self):
        if platforms.get_platform() == 'rpi':
            return True
        else:
            self.write_message('SSH access configuration available only on Raspberry Pi', show_time = 3)

    def generate_ssh_keys(self):
        if self.check_if_rpi():
            private_key_path = rights.generate_ssh_keys()
            if private_key_path:
                headers = {}
                headers['Content-Type'] = 'application/octet-stream'
                headers['Content-Disposition'] = 'attachment; filename=' + os.path.basename(private_key_path)
                headers['Content-Transfer-Encoding'] = 'binary'
                headers['Accept-Ranges'] = 'bytes'
                with open(private_key_path) as f:
                    key = f.read()
                self.status = 200
                self.headers = headers
                self.body = key
                os.remove(private_key_path)
            else:
                message = 'An error occurred during keys generation.'
                self.write_message(message, show_time=5)

    def revoke_all_ssh_keys(self):
        if self.check_if_rpi():
            if rights.revoke_all_ssh_keys():
                message = 'All SSH key are revoked. Now image can only be accessed using password.'
            else:
                message = 'An error occurred during keys revoking.'
            self.write_message(message, show_time=5)

    def add_public_key(self):
        if not self.check_if_rpi():
            status = 403
            message = "Only allowed on RaspberryPi"
        else:
            try:
                public_key = self.parse_post('key').file.read()
                public_key = public_key.decode()
                password = self.parse_post('password')
                self.logger.info('Adding public key:\n' + public_key)
                #self.logger.info('Adding public key:\n' + password)
            except Exception as e:
                message = 'Error adding public key'
                status = 500
                self.logger.warning(message + ":" + str(e))
            else:
                try:
                    result = pam.authenticate(os.getlogin(), password, service='sudo') #service sudo is essential for this to work on RPi
                except:
                    message = "PAM authentication error"
                    self.logger.exception(message)
                    status = 452
                else:
                    if result:
                        if rights.add_public_key(public_key):
                            message = 'SSH key was added to authorized keys. You can access image using it.'
                            status = 200
                        else:
                            message = 'Error adding public key'
                            status = 452
                            self.logger.warning(message)
                    else:
                        message = "Wrong password"
                        status = 403
            self.write_message(message, response=status, show_time=5)

    def network_printers_list(self):
        network_printers_detector = self.app.detectors.get('NetworkDetector')
        if network_printers_detector:
            printers_list = network_printers_detector.printers_list_with_profiles()
        else:
            printers_list = []
        printers_list = json.dumps(printers_list)
        self.logger.info("Network printers list:\t" + printers_list)
        self.headers['Content-Type'] = 'application/json'
        self.body = printers_list.encode("utf-8")

    def forget_network_printer(self):
        usb_info_json = self.parse_post('usb_info')
        self.logger.info("Forgetting network printer: " + str(usb_info_json))
        usb_info = json.loads(usb_info_json)
        network_printers_detector = self.app.detectors.get('NetworkDetector')
        if network_printers_detector:
            network_printers_detector.forget_printer(usb_info)

    def process_logout(self):
        self.app.user_login.logout()
        self.app.restart_flag = True
        self.quit_main_app()

    def plugins_list(self):
        plugins_list = list(self.app.plugin_controller.plugins.values())
        plugins_list = json.dumps(plugins_list)
        self.logger.info("Printers list:\t" + plugins_list)
        self.headers['Content-Type'] = 'application/json'
        self.body = plugins_list.encode("utf-8")

    def control_plugin(self):
        command = self.parse_post('command')
        try:
            command = json.loads(command)
            if type(command) != dict:
                raise TypeError
        except (TypeError, ValueError):
            self.write_message("Error: invalid contents of field command: %s" % command)
        else:
            action = command['action']
            method_name = action + "_plugin"
            method = getattr(self.app.plugin_controller, method_name, None)
            if method:
                method(command["plugin_name"])
            else:
                self.write_message("Error: no such plugin action: " + method_name)

    def accept_and_install_plugin_file(self):
        error = self.parse_and_save_multipart_file_upload()
        if error:
            message = 'Failed to accept plugin package! %s' % error
            self.logger.warning(message)
            self.write_message(message, show_time=0, button_text='Back')
        else:
            name = self.app.plugin_controller.install(paths.PLUGIN_INSTALL_FILE_PATH)
            self.app.plugin_controller.load_plugin(name)
            os.remove(paths.PLUGIN_INSTALL_FILE_PATH)
            self.write_with_autoreplace(self.web_content_files['templates']['pages']['plugins_page.html'])

    def print_from_file_or_delete(self):
        filename, printer_id, action = self.parse_post('filename', 'printer_id', 'action')
        filepath = os.path.join(paths.STORAGE_FOLDER, filename)
        self.logger.info("Printer: %s|Action: %s|Filename: %s" % (printer_id, action, filename))
        if action == "Delete":
            try:
                os.remove(filepath)
            except (OSError, IOError):
                message = "Error while removing file %s" % filename
                self.write_message(message)
            else:
                self.local_file_print_or_delete(printer_id)
        elif action == "Print":
            if printer_id and filepath:
                try:
                    printer_interface = self.find_printer_interface(printer_id)
                    if not printer_interface or not printer_interface.sender.gcodes(filepath, keep_file=True):
                        raise RuntimeError
                    printer_interface.sender.set_filename(filename)
                except (RuntimeError, AttributeError):
                    message = "Printer had become not operational while loading file %s" % filename
                    self.logger.warning(message)
                    self.write_message(message)
                except Exception as e:
                    message = f"Error while loading file {filename}.\n{e}"
                    self.logger.exception(message)
                    self.write_message(message)
                else:
                    self.write_message("File load success. Starting print...")
            else:
                self.write_message("Error: invalid printer ID or file path %s %s" % (printer_id, filepath))

    def upload_to_storage(self):
        error = self.parse_and_save_multipart_file_upload()
        if error:
            self.write_message("Upload failed: %s" % error, show_time=0, button_text='Back')
        else:
            self.write_message("Upload success")

    def upload_to_storage_page(self):
        self.write_with_autoreplace(self.web_content_files['templates']['pages']['upload_to_storage.html'])

    def local_file_print_or_delete(self, printer_id=None):
        if not printer_id:
            printer_id = self.parse_post("printer_id")
        page = self.web_content_files['templates']['pages']['local_file_print_or_delete.html']
        page = page.replace("!!!PRINTER_ID!!!", printer_id)
        page = page.replace("!!!FILES!!!", self.form_storage_files_radio_list())
        page = page.replace("!!!FREE_SPACE!!!", paths.get_human_free_space(paths.STORAGE_FOLDER))
        self.logger.info("Entering local print page for printer:%s" % printer_id)
        self.write_with_autoreplace(page)

    def form_storage_files_radio_list(self):
        radio_template = '<input type="radio" name="filename" value="VALUE">VIEWED_LINE<br />'
        viewed_template = '<b>%s</b>&emsp;<i>%s</i>&emsp;<b>%s</b>'
        files_list = []
        for filename in os.listdir(paths.STORAGE_FOLDER):
            filepath = os.path.join(paths.STORAGE_FOLDER, filename)
            access_time = os.path.getatime(filepath)
            human_access_time = paths.get_human_access_time(filepath)
            size = paths.get_human_file_size(filepath)
            files_list.append({"name": filename, "size": size, "time": access_time, "h_time": human_access_time})
        files_list.sort(key=lambda f: f['h_time'])
        output = ''
        for f in files_list:
            viewed = viewed_template % (f['name'], f['size'], f['h_time'])
            output += radio_template.replace("VALUE", f['name']).replace("VIEWED_LINE", viewed)
        return output

    #NOTE exist only for compatibility with old code - aiohttp will fill the variable
    #TODO remove because aiohttp is handling all posts now
    def parse_and_save_multipart_file_upload(self, path=None):
        return self.post_data['multipart_error']

    def get_detection_wizard_page(self):
        wizard = self.app.detection_wizard
        confirm_page_template = self.web_content_files['templates']['pages']['confirm.html']
        wizard_template = self.web_content_files['templates']['pages']['detection_wizard_page.html']
        if self.app.stop_flag:
            wizard.stop()
            self.write_message("Detection canceled by application quit...", show_time=1)
        elif not wizard.active:
            warning = self.htmlify_text(wizard.get_warning_text())
            page = confirm_page_template.replace("!!!BODY!!!", warning)
            page = page.replace("!!!CONFIRM_PATH!!!", "detection_wizard_start")
            page = page.replace("Confirm", "Proceed")
            self.write_with_autoreplace(page)
        else:
            template = '<input type="radio" name="usb_info" value="%s">%s&emsp;<b>%s</b><br />'
            add_locally_button_mark = "!!!ADD_LOCALLY_BUTTON!!!"
            add_locally_button_html = '<input type="submit" name="action" value="Add locally" style="display: inline;\
                    margin-bottom: 0px;" class="big_long_button">'
            devices = wizard.device_status_lines_list_by_id()
            self.logger.debug("Detected devices: %s" % devices)
            lines = [template % device_line for device_line in devices]
            if lines:
                if wizard.scan_finised():
                    wizard_template = wizard_template.replace("Scanning...", "Select a printer to add locally or to add to cloud")
                devices_html = self.htmlify_text("\n".join(lines), ignore_endofline=True)
                wizard_template = wizard_template.replace(add_locally_button_mark, add_locally_button_html)
                self.write_with_autoreplace(wizard_template.replace("!!!DEVICES!!!", devices_html))
            elif wizard.scan_finised():
                devices_html = "Unable to detect any 3D printers"
                wizard_template = wizard_template.replace("Scanning...", "")
                wizard_template = wizard_template.replace(add_locally_button_mark, "")
                self.write_with_autoreplace(wizard_template.replace("!!!DEVICES!!!", devices_html))
            else:
                self.write_message("Scanning for USB devices...", show_time=2, redirect="/detection_wizard_page", button_text="Cancel")

    def detection_wizard_start(self):
        error = self.app.detection_wizard.start_scan()
        if error:
            self.write_message(error)
        else:
            self.write_message("Starting Detection Wizard...", show_time=1, redirect="/detection_wizard_page")

    def detection_wizard_action(self):
        detection_wizard = self.app.detection_wizard
        results = self.parse_post("action", "usb_info")
        if type(results) in (list, tuple) and len(results) > 1:
            action, usb_info = results
        else:
            action = results
            usb_info = None
        self.logger.debug("Detection wizard action and usb_info: %s %s" % (action, usb_info))
        if action == "Cancel":
            detection_wizard.stop()
            self.write_message("Detection canceled")
        elif not usb_info:
            self.write_message("Please select a printer", show_time=2, redirect="/detection_wizard_page")
        elif action == "Add locally":
            error = detection_wizard.save_profile_locally(usb_info)
            if error:
                self.write_message("Error while saving profile:" + error, show_time=2, redirect="/detection_wizard_page")
            else:
                detection_wizard.stop()
                self.write_message("Printer profiles added. Now you can use them to print locally.")
        elif action == "Go to integration form...":
            self.detection_wizard_integration_form(usb_info)
        else:
            self.write_message("Error: unknown action %s" % action, show_time=2, redirect="/detection_wizard_page")

    def detection_wizard_form_action(self):
        field_names = "model", "manufacturer", "dimensions", "details", "usb_info"
        post_list = self.parse_post(*field_names)
        form_text = ""
        if post_list:
            for index, field in enumerate(field_names):
                if post_list[index]:
                    pretty_field_name = field[0].capitalize() + field[1:]
                    form_text += "%s: %s\n" % (pretty_field_name, str(post_list[index]))
        error = self.app.detection_wizard.create_and_send_report(form_text)
        if error:
            message = error
        else:
            message = "Printer integration request was successfully submitted"
        self.write_message(message, show_time=3, redirect="/detection_wizard_page")

    def detection_wizard_integration_form(self, usb_info_str):
        page = self.web_content_files['templates']['pages']['detection_wizard_integration_form.html']
        page = page.replace("!!!PRINTER_ID!!!", usb_info_str)
        try:
            usb_info = json.loads(usb_info_str.replace("'", '"'))
        except (TypeError, ValueError):
            error = "Error decoding usb_info string:%s" % usb_info_str
            self.logger.error(error)
            self.write_message(error, show_time=2, redirect="/detection_wizard_page")
        else:
            dc = self.app.detection_wizard.get_device_container_by_usb_info(usb_info)
            text_line = self.app.detection_wizard.device_line(dc)
            page = page.replace("!!!TEXT_LINE!!!", text_line)
            self.write_with_autoreplace(page)

    def send_detection_wizard_report(self):
        detection_wizard = self.app.detection_wizard
        error = detection_wizard.create_and_send_report()
        if error:
            self.write_message("Error: %s. Please try again." % error, show_time=5, redirect="/detection_wizard_page")
        else:
            detection_wizard.stop()
            self.write_message("Your integration request was successfully submitted", show_time=2, redirect="/detection_wizard_page")

    def form_confirm_page(self, body, confirm_path):
        page = self.web_content_files['templates']['pages']['confirm.html']
        page = page.replace('!!!BODY!!!', body)
        page = page.replace('!!!CONFIRM_PATH!!!', confirm_path)
        return page

    def form_quit_confirm_page(self):
        message = '<h3>Quit</h3>Are you sure you want to exit 3DPrinterOS Client?<br />'
        'All your active prints will be interrupted.'
        if self.reset_printer_type:
            message = message.replace('exit', 'restart')
            message = message.replace('Quit', 'Restart confirmation')
        return self.form_confirm_page(message, 'confirm_quit')

    def form_logout_confirm_page(self):
        return self.form_confirm_page('<h3>Logout</h3>Are you sure want to logout?<br />'
                                      'This will close the application and all active prints will be interrupted.',
                                      'confirm_logout')

    def form_main_page(self):
        update_complete_path = os.path.join(paths.CURRENT_SETTINGS_FOLDER, 'update_complete')
        if os.path.exists(update_complete_path):
            try:
                os.remove(update_complete_path)
            except Exception as e:
                self.logger.error(f'Update to remove {update_complete_path}, but it exists. Error: {e}')
            else:
                return self.web_content_files['templates']['pages']['update_complete.html']
        if self.app.stop_flag:
            return self.form_closing_page()
        #TODO use user_login.waiting for better support of apiprinter mode
        if not self.app.offline_mode and not self.app.user_login.user_token:
            name = 'login.html'
        elif self.app.rights_checker.waiting:
            return self.web_content_files['templates']['pages']['groups_warning.html']
        elif self.app.conveyor_kill_waiter.waiting:
            return self.web_content_files['templates']['pages']['conveyor_warning.html']
        elif self.wizard_enabled:
            return self.write_message('Starting tutorial wizard', show_time=1, redirect="/wizard/", return_page=True)
        else:
            name = 'main.html'
            if hasattr(self.app, 'detection_wizard') and self.app.detection_wizard.active:
               self.app.detection_wizard.stop() # disable detection wizard if we go to main page
        page = self.web_content_files['pages'][name]
        login = self.app.user_login.login
        if login:
            mac = self.app.host_id
        else:
            login = ""
            mac = ""
            if self.app.offline_mode:
                login = "Offline mode"
        page = page.replace('!!!LOGIN!!!', login)
        page = page.replace('!!!HOST_MAC!!!', mac)
        return page

    def place_virtual_printer_button(self, page):
        virtual_printer_button = self.web_content_files['templates']['buttons']['toggle_button.html']
        if self.app.virtual_printer_enabled:
            action = 'Disable virtual printer'
        else:
            action = 'Enable virtual printer'
        virtual_printer_button = virtual_printer_button.replace('!!!ACTION!!!', action)
        virtual_printer_button = virtual_printer_button.replace('!!!PATH!!!', 'toggle_virtual_printer')
        return page.replace('!!!VIRTUAL_PRINTER_BUTTON!!!', virtual_printer_button)

    def place_gpio_button(self, page):
        if platforms.PLATFORM == "rpi":
            gpio_button = self.web_content_files['templates']['buttons']['toggle_button.html']
            if getattr(self.app, 'gpio_interface', False) and self.app.gpio_interface and self.app.gpio_interface.enabled:
                action = 'Disable GPIO interface'
            else:
                action = 'Enable GPIO interface'
            gpio_button = gpio_button.replace('!!!ACTION!!!', action)
            gpio_button = gpio_button.replace('!!!PATH!!!', 'toggle_gpio')
        else:
            gpio_button = ''
        return page.replace('!!!GPIO_BUTTON!!!', gpio_button)

    def place_toggle_prt_button(self, page):
        button = self.web_content_files['templates']['buttons']['toggle_button.html']
        if self.config['backward_compatible_serials']:
            action = 'Switch to new style SNR'
        else:
            action = 'Switch to old style SNR'
        button = button.replace('!!!ACTION!!!', action).replace('!!!PATH!!!', 'toggle_prt_in_dual_serial')
        return page.replace('!!!PRT_BUTTON!!!', button)

    def place_toggle_intercept_pause_button(self, page):
        button = self.web_content_files['templates']['buttons']['toggle_button.html']
        if self.config['intercept_pause']:
            action = 'Pass pause gcodes'
        else:
            action = 'Catch pause gcodes'
        button = button.replace('!!!ACTION!!!', action).replace('!!!PATH!!!', 'intercept_pause')
        return page.replace('!!!PAUSE_INTERCEPT_BUTTON!!!', button)

    def place_toggle_clisma_button(self, page):
        if platforms.PLATFORM == "rpi":
            button = self.web_content_files['templates']['buttons']['toggle_button.html']
            if rights.is_clisma_running():
                action = 'Disable support access'
            else:
                action = 'Enable support access'
            button = button.replace('!!!ACTION!!!', action).replace('!!!PATH!!!', 'toggle_clisma')
        else:
            button = ''
        return page.replace('!!!CLISMA_BUTTON!!!', button)

    def place_toggle_verbose_button(self, page):
        button = self.web_content_files['templates']['buttons']['toggle_button.html']
        if self.config['verbose']:
            action = 'Disable verbose logging'
        else:
            action = 'Enable verbose logging'
        button = button.replace('!!!ACTION!!!', action).replace('!!!PATH!!!', 'toggle_verbose')
        return page.replace('!!!VERBOSE_BUTTON!!!', button)

    def place_toggle_uart_button(self, page):
        button = ''
        if platforms.PLATFORM == "rpi":
            button = self.web_content_files['templates']['buttons']['toggle_button.html']
            if config.get_settings()['gpio_uart'].get('enabled', False):
                button = button.replace('!!!ACTION!!!', 'Disable GPIO UART').replace('!!!PATH!!!',\
                                                        'disable_uart')
            else:
                button = button.replace('!!!ACTION!!!', 'Enable GPIO UART').replace('!!!PATH!!!',\
                                                        'uart_confirmation_page')
        return page.replace('!!!UART_BUTTON!!!', button)

    def form_manage_printer_page(self, usb_info_string):
        printer_interface = self.find_printer_interface(usb_info_string)
        if printer_interface:
            page = self.web_content_files['templates']['pages']['manage_printer.html']
            return page.replace('!!!PRINTER_ID!!!', usb_info_string) \
                .replace('!!!ID!!!', 'manage_printer_button')

    def form_rename_printer_page(self, usb_info_string):
        printer_interface = self.find_printer_interface(usb_info_string)
        if printer_interface:
            page = self.web_content_files['templates']['pages']['rename_printer.html']
            return page.replace('!!!PATH!!!', 'set_new_printer_name') \
                .replace('!!!PRINTER_ID!!!', usb_info_string) \
                .replace('!!!CURRENT_NAME!!!', printer_interface.printer_name) \
                .replace('!!!ID!!!', 'printer_rename_button')

    def form_printer_groups_page(self, usb_info_string):
        printer_interface = self.find_printer_interface(usb_info_string)
        if printer_interface:
            printer_groups = printer_interface.get_groups()
            groups_checkboxes = ''
            for index, group in enumerate(printer_groups):
                if group['active']:
                    checked_html = ' checked '
                else:
                    checked_html = ''
                groups_checkboxes += '<input type="checkbox" name="selected_group_id_' + str(index) + '" value="' \
                                     + str(group['id']) + '" ' + checked_html + '> ' \
                                     + group['name'] + '<br />'
            if not groups_checkboxes:
                groups_checkboxes = 'No groups or group management is unavailable'
            page = self.web_content_files['templates']['pages']['select_printer_groups.html']
            page = page.replace('!!!PATH!!!', 'confirm_printer_groups/' + usb_info_string)
            return page.replace('!!!CHECKBOXES!!!', groups_checkboxes)

    def form_back_button(self, back_path):
        return self.web_content_files['templates']['buttons']['back_button.html'].replace('!!!BACK_PATH!!!', back_path)

    def form_type_select_page(self, usb_info_string):
        printer_interface = self.find_printer_interface(usb_info_string)
        if printer_interface:
            selector = self.form_printer_type_selector(printer_interface.possible_printer_types)
            page = self.web_content_files['templates']['pages']['choose_printer_type.html']
            return page.replace('!!!TYPE_SELECT!!!', selector) \
                                .replace('!!!PATH!!!', 'choose_printer_type')\
                                .replace('!!!PRINTER_ID!!!', usb_info_string)

    def form_printer_type_selector(self, possible_printer_types):
        selector = self.web_content_files['templates']['selectors']['printer_type_selector.html']
        options = ''
        for printer_type in possible_printer_types:
            printer_name = printer_type.get('name')
            required_fields = printer_type.get('required_fields', [])
            required_fields_attr = ""
            if required_fields:
                required_fields_attr = 'required_fields="' + ','.join(required_fields) + '"'
            options += '<option value="' + printer_name + '" ' + required_fields_attr + '"> ' + printer_name + '</option>'
        return selector.replace('!!!OPTIONS!!!', options)

    def form_connect_to_network_printer_page(self):
        possible_printer_types = []
        network_printers_detector = self.app.detectors.get('NetworkDetector')
        if network_printers_detector:
            for profile in network_printers_detector.get_all_network_profiles():
                required_fields = []
                for conn in profile.get('v2', {}).get('connections', []):
                    auth_fields = conn.get('auth_fields', {})
                    for auth in auth_fields:
                        if type(auth) == dict and "id" in auth:
                            field = auth['id']
                            if field != 'IP':
                                required_fields.append(field)
                if not required_fields:
                    required_fields = profile.get("network_detect", {}).get('required_fields', [])

                possible_printer_types.append({'name': str(profile.get('name')),
                                               'required_fields': required_fields})
        printer_type_selector = self.form_printer_type_selector(possible_printer_types)
        page = self.web_content_files['templates']['pages']['connect_to_network_printer.html']
        page = page.replace('!!!TYPE_SELECT!!!', printer_type_selector)
        return page

    def form_type_reset_page(self, usb_info_string):
        printer_interface = self.find_printer_interface(usb_info_string)
        profile = getattr(printer_interface, 'printer_profile', None)
        if profile:
            return self.web_content_files['templates']['pages']['request_printer_type_reset.html']\
                .replace('!!!PRINTER_TYPE!!!', profile['name'])\
                .replace('!!!PATH!!!', 'reset_printer_type')\
                .replace('!!!PRINTER_ID!!!', usb_info_string)

    def form_choose_cam_page(self):
        if hasattr(self.app, 'camera_controller'):
            camera_selector = self.form_camera_mode_selector()
        else:
            camera_selector = ''
        page = self.web_content_files['templates']['pages']['choose_camera.html']
        page = page.replace('!!!MODULES_SELECT!!!', camera_selector)
        return page

    def form_camera_mode_selector(self):
        module_selector_html = ''
        if hasattr(self.app, 'camera_controller'):
            modules = self.app.camera_controller.CAMERA_MODULES
            for module in list(modules.keys()):
                if module == self.app.camera_controller.current_camera_name:
                    module_selector_html += \
                        '<p><input type="radio" name="module" checked value="' + module + \
                        '"><font color="lightgrey" title="Current live view mode">'\
                        + module + '</font></p>'
                else:
                    module_selector_html += '<p><input type="radio" name="module" value="' \
                                            + module + '"> ' + module + '</p>'
        return module_selector_html

    def form_hd_camera_start_page(self):
        page = self.web_content_files['templates']['pages']['start_hd_camera.html']
        options = ''
        for printer_interface in self.app.printer_interfaces:
            try:
                if hasattr(printer_interface, 'printer_profile'):
                    options += '<option value="' \
                                            + printer_interface.printer_token + '"> ' \
                                            + printer_interface.printer_profile['name']\
                                            + '(' + printer_interface.printer_name + ')' + '</option>'
            except (KeyError, AttributeError, TypeError):
                pass
        if options:
            selector = self.web_content_files['templates']['selectors']['hd_cam_printer_selector.html']\
                .replace('!!!OPTIONS!!!', options)
            page = page.replace('!!!PRINTER_SELECT!!!', selector)
            return page

    def form_get_updates_page(self):
        return self.web_content_files['templates']['pages']['update_software.html']

    def form_update_downloaded_page(self):
        return self.web_content_files['templates']['pages']['update_downloaded.html']

    def form_report_problem_page(self):
        return self.web_content_files['templates']['pages']['report_problem.html']

    def form_show_logs_page(self):
        page = self.web_content_files['templates']['pages']['show_logs.html']
        return page

    def form_closing_page(self):
        page = self.web_content_files['templates']['pages']['closing.html']
        return page

    def form_closing_message(self):
        message = ''
        for item in self.app.closing_status:
            message += item + '<br />'
        return message

    def form_detect_network_clients_page(self):
        detected_clients = self.app.client_scanner.detect()
        if detected_clients:
            html = ''
            for ip, client in list(detected_clients.items()):
                url = "http://%s:%s/" % (ip, client['port'])
                display_line = "%s(%s) - %s" % (ip, client['platform'], client['login'])
                if client['remote_control']:
                    html += '<a target="_blank" href=%s>%s</a><br>' % (url, display_line)
                else:
                    html += '%s<br>' % display_line
                if client['printers']:
                    for printer in client['printers']:
                        html += printer['name']
                        if printer['profile']:
                             html += ': %s' % printer['profile']['name']
                        html += '<br>'
                else:
                    html += 'No printers detected on this machine<br>'
                html += '<hr width="90%">'
            page = self.web_content_files['templates']['pages']['detect_clients.html']
            self.write_with_autoreplace(page.replace('!!!SELECTOR!!!', html))
        else:
            self.write_message('No 3DPrinterOS Clients detected in your network', show_time=5)

    def form_joystick_page(self, printer_id):
        return self.web_content_files['templates']['pages']['joystick.html']\
            .replace('!!!PRINTER_ID!!!', printer_id)

    def form_edit_settings_page(self):
        page = self.web_content_files['templates']['pages']['edit_settings.html']
        settings = json.dumps(self.config, sort_keys = True, indent = 4, separators = (',', ': '))
        page = page.replace('!!!CURRENT_SETTINGS!!!', settings)
        return page

    # TODO unify toggle buttons functionality
    def form_manage_settings_page(self):
        if self.config['update']['enabled']:
            action_name = 'Disable updates'
        else:
            action_name = 'Enable updates'
        update_section = self.web_content_files['templates']['buttons']['toggle_button.html']\
            .replace('!!!PATH!!!', 'settings/toggle_update')\
            .replace('!!!ACTION!!!', action_name)
        update_section += '<form action="/update_from_file" method="get">\
                    <input type="submit" value="Update from file" class="middle_button" style="margin: 2px;">\
                    </form>'
        if self.config['update']['enabled']:
            if self.config['update']['auto_update_enabled']:
                action_name = 'Disable auto updates'
            else:
                action_name = 'Enable auto updates'
            update_section += self.web_content_files['templates']['buttons']['toggle_button.html']\
                .replace('!!!PATH!!!', 'settings/toggle_auto_update')\
                .replace('!!!ACTION!!!', action_name)
            update_section += '<h5>Update check pause (seconds)</h5>' + self.web_content_files['templates']['forms']['set_parameter_form.html']\
                .replace('!!!PATH!!!', 'settings/set_update_check_pause')\
                .replace('!!!PARAMETER_NAME!!!', 'check_pause')\
                .replace('!!!PARAMETER_DISPLAY_NAME!!!', 'Check pause (minutes)')\
                .replace('!!!DEFAULT_VALUE!!!', str(self.config['update']['check_pause']))
        page = self.web_content_files['templates']['pages']['manage_settings.html']\
            .replace('!!!UPDATE_SECTION!!!', update_section)
        page = self.place_virtual_printer_button(page)
        page = self.place_gpio_button(page)
        page = self.place_toggle_prt_button(page)
        page = self.place_toggle_clisma_button(page)
        page = self.place_toggle_verbose_button(page)
        page = self.place_toggle_intercept_pause_button(page)
        page = self.place_toggle_uart_button(page)
        return page

    def form_local_mode_warning_page(self, printer_id):
        return self.web_content_files['templates']['pages']['local_mode_warning.html']\
            .replace('!!!PRINTER_ID!!!', printer_id)

    def form_print_from_file_page(self):
        printer_selector_html = ''
        for pi in self.app.printer_interfaces:
            if pi.printer_name:
                name = pi.name
            else:
                name = str(pi.printer_profile)
            printer_selector_html += \
                '<p><input type="radio" name="printer" checked value="' + name + \
                '"><font color="lightgrey" title="Printers">'\
                + name + '</font></p>'
        page = self.web_content_files['templates']['pages']['print_from_file.html']
        page = page.replace('!!!PRINTER_SELECT!!!', printer_selector_html)
        return page

    def form_edit_network(self):
        page = self.web_content_files['templates']['pages']['manage_network.html']
        self.write_with_autoreplace(page)

    def edit_network(self):
        post = self.parse_post('enabled','http_proxy', 'https_proxy')
        if len(post) != 3:
            self.write_message('Error parsing proxy settings', show_time=5)
        else:
            enabled, http_proxy, https_proxy = post
            # self.logger.info('enabled:' + enabled)
            # self.logger.info('http_proxy:' + http_proxy)
            # self.logger.info('https_proxy:' + https_proxy)
            if enabled == 'true':
                enabled = True
            else:
                enabled = False
            self.config['protocol']['proxies']['enabled'] = enabled
            if enabled:
                self.config['protocol']['proxies']['http'] = http_proxy
                self.config['protocol']['proxies']['https'] = https_proxy
            config.Config.instance().save_settings(self.config)
            self.write_message('Proxy settings where successfully applied and saved.', show_time=3)

    def form_change_ssh_password(self):
        page = self.web_content_files['templates']['pages']['change_ssh_password.html']
        self.write_with_autoreplace(page)

    def change_ssh_password(self):
        old_password, new_password, confirm_new_password = self.parse_post('old_password','new_password', 'confirm_new_password')
        if new_password != confirm_new_password:
            pass
        error = rights.change_ssh_password(old_password, new_password)
        if not error:
            self.write_message('Password changed', show_time=3)
        else:
            self.write_message(f'Error: {error}', show_time=6)

    def get_config_value(self):
        #TODO remove this nonsense code
        path = self.path.split('?')[0].strip('/').split('/')
        settings = {}
        settings.update(self.config)
        if len(path) <= 2:
            self.logger.warning(f'Cant find setting for path {self.path}. Use path: /config/get/outer_settings_name/inner_setting_name/etc')
            return ""
        else:
            path = path[2:]
            for path_comp in path:
                if settings and path_comp in settings:
                    settings = settings.get(path_comp)
                else:
                    self.logger.warning(f'Cant find setting for path {self.path}. Unknown value: {path_comp}')
                    return ""
            self.logger.info(f'Setting for path:{path} = {settings}')
            return json.dumps(settings)

    def get_compat_template(self):
        last_path = self.path.split('/')[-1]
        if last_path == 'version':
            return "v.%s.%s_%s" % (version.version, version.build, version.branch)
        elif last_path == 'url':
            return self.cloud_url.replace("cli-cloud", "cloud")
        elif last_path == 'style':
            return self.config.get("web_interface", {}).get("style", "style") + '.css'
        elif last_path == 'files':
            return self.form_storage_files_radio_list()
        elif last_path == 'free_space':
            return paths.get_human_free_space(paths.STORAGE_FOLDER)
        elif last_path == 'platform':
            return platforms.get_platform()
        elif last_path == 'login':
            return self.app.user_login.login
        elif last_path == 'host_mac':
            if self.app.offline_mode:
                login = "Offline mode"
            else:
                login = self.app.macaddr
            return login
        elif last_path == 'your_account_button':
            if self.show_your_account_button:
                return self.web_content_files['templates']['buttons']['your_account_button.html']
            else:
                return ''
        # elif last_path == 'button':
        #     # button_html = self.web_content_files['templates']['buttons']['generic_button.html']
        #     # button_html = button_html.replace("%VALUE%", button_text)
        #     # button_html = button_html.replace("%PATH%", button_url)
        #     # button_html = button_html.replace("%METHOD%", "get")
        #     # return button_html.replace("%CLASS%", "big_button")
        # elif last_path == 'printer_id':
        #     login = self.app.user_login.login
        elif last_path == 'modules_select':
            return self.form_camera_mode_selector()
        elif last_path == 'type_select':
            possible_printer_types = []
            network_printers_detector = self.app.detectors.get('NetworkDetector')
            if network_printers_detector:
                for profile in self.app.network_printers_detector.get_all_network_profiles():
                    possible_printer_types.append({'name': str(profile.get('name'))})
            printer_type_selector = self.form_printer_type_selector(possible_printer_types)
            return printer_type_selector
        elif last_path == 'current_settings':
            return json.dumps(self.config, sort_keys = True, indent = 4, separators = (',', ': '))
        else:
            self.status = 404
            return f'Unknown or unimplemented template: {self.path}'

    def printers_ids(self):
        self.body = json.dumps([pi.id_string for pi in self.app.printer_interfaces])
        self.status = 200
        self.headers = {'Content-Type':'application/json'}

    def printer_by_id(self):
        try:
            path = self.path.split('/')[-1]
        except:
            self.logger.warning('Unable to parse path for printer id')
        else:
            pi = self.find_printer_interface(path)
            if pi:
                printer = pi.state_report()
                errors = pi.errors
                if errors:
                    printer['errors'] = errors
                self.body = json.dumps(printer)
                self.status = 200
                self.headers = {'Content-Type':'application/json'}
                return
        self.body = 'No printer with such ID'
        self.status = 452

    def uart_confirmation_page(self):
        if config.get_settings()['gpio']['enabled'] and not config.get_settings()['gpio_uart']['allow_use_with_buttons']:
            self.write_message('Disable GPIO interface enabling UART', back_button_to='/manage_settings', show_time=0)
        else:
            enabled_in_config = False
            try:
                with open('/boot/config.txt') as f:
                    text = f.read()
                    text = text.replace(" ", "")
                    text = text.replace("\t", "")
                    char_number = text.find("enable_uart=1")
                    if char_number != -1:
                        if char_number != 0 or text[char_number-1] == "\n":
                            enabled_in_config = True
            except OSError:
                pass
            if enabled_in_config:
                settings = config.get_settings()
                settings['active_detectors']['GPIOUARTDetector'] = True
                settings['gpio_uart']['enabled'] = True
                config.Config.instance().save_settings(settings)
                self.write_with_autoreplace(self.form_manage_settings_page())
            else:
                self.write_message('Restart required to enable the UART. Warning your prints on this machine will aborted!', button_text='Restart', button_url='enable_uart', button_method='post', back_button_to='/manage_settings', show_time=0)

    def enable_uart(self):
        if not self.app.gpio_interface:
            try:
                if os.path.exists(paths.DISABLE_UART_PATH):
                    os.remove(paths.DISABLE_UART_PATH)
                with open(paths.ENABLE_UART_PATH, "a") as f:
                    f.write("")
            except Exception as e:
                self.logger.error("Error create a file flag to enable UART pin" + str(e))
            settings = config.get_settings()
            settings['active_detectors']['GPIOUARTDetector'] = True
            settings['gpio_uart']['enabled'] = True
            config.Config.instance().save_settings(settings)
            self.write_with_autoreplace(self.form_closing_page())
            self.app.poweroff_flag = True
            self.app.restart_flag = True
            self.app.stop_flag = True
        else:
            self.write_message('Disable GPIO interface before enabling UART', back_button_to='/manage_settings', show_time=0)

    def disable_uart(self):
        try:
            if os.path.exists(paths.ENABLE_UART_PATH):
                os.remove(paths.ENABLE_UART_PATH)
            with open(paths.DISABLE_UART_PATH, "a") as f:
                f.write("")
            settings = config.get_settings()
            settings['active_detectors']['GPIOUARTDetector'] = False
            settings['gpio_uart']['enabled'] = False
            config.Config.instance().save_settings(settings)
        except Exception as e:
            self.logger.error("Error create a file flag to disable UART pin: " + str(e))
        self.write_with_autoreplace(self.form_manage_settings_page())

    def json_printers_status(self):
        all_printer_dicts = {}
        for pi in self.app.printer_interfaces:
            pi_dict = {}
            pi_dict['ids'] = pi.usb_info
            pi_dict['report'] = pi.state_report()
            pi_dict['errors'] = pi.get_errors_to_send('PF_' + self.remote)
            all_printer_dicts[str(pi)] = pi_dict
        self.body = json.dumps(all_printer_dicts)

    def get_conn_types_list_html(self):
        printer_id = self.parse_post('printer_id')
        printer_interface = self.find_printer_interface(printer_id)
        conn_types_options_html = ''
        vid, pid = printer_id.split('_')[:2]
        if printer_interface:
            if printer_interface.possible_conn_types:
                for conn in printer_interface.possible_conn_types:
                    for conn_id_dict in conn['ids']:
                        if conn_id_dict.get('VID') == vid and conn_id_dict.get('PID') == pid:
                            if printer_interface.connection_id == conn['id']:
                                selected = " selected "
                            else:
                                selected = " "
                            conn_types_options_html += "<option" + selected + 'value="' + conn['id'] + '">' + conn["name"] + '</option>'
                            break
        return conn_types_options_html

    def show_printer_conn_type_page(self):
        page = self.web_content_files['templates']['pages']['select_conn_type.html']
        printer_id = self.post_data.get("printer_id")
        if self.find_printer_interface(printer_id):
            page = page.replace('!!!PRINTER_ID!!!', printer_id)
            page = page.replace('!!!CONN_TYPES!!!', self.get_conn_types_list_html())
            self.write_with_autoreplace(page)
        else:
            self.write_message('Lost connection to the printer. Please, try again.', show_time=2, response=400, redirect="/", button_text="", button_url="/", button_method="get", back_button_to="")

    def select_printer_conn_type(self):
        printer_id = self.post_data.get("printer_id", "")
        conn_type_id = self.post_data.get("conn_type", "")
        printer_interface = self.find_printer_interface(printer_id)
        if printer_interface:
            if printer_interface.get_printer_state() in ('printing', 'paused', 'downloading'):
                if printer_interface.set_connection(conn_type_id, apply_restart=True):
                    message = "Printer is busy. Connection type change delayed until next connection"
                else:
                    message = "Error: unknown connection type"
            else:
                if printer_interface.set_connection(conn_type_id, apply_restart=True):
                    message = "Connection type change success"
                else:
                    message = "Error: unknown connection type"
            self.write_message(message, show_time=5, response=200, redirect="/", button_text="", button_url="/", button_method="get", back_button_to="")
        else:
            self.write_message('Lost connection to the printer. Please, try again.', show_time=2, response=400, redirect="/", button_text="", button_url="/", button_method="get", back_button_to="")

    # def show_printer_cloud_binding_page(self):
    #     printer_id = self.post_data.get("printer_id", "")
    #     printer_interface = self.find_printer_interface(printer_id)
    #     if printer_interface:
    #         page = self.web_content_files['templates']['pages']['printer_cloud_binding.html']
    #         page = page.replace('!!!PRINTER_ID!!!', printer_id)
    #         page = page.replace('!!!CLOUD_PRINTERID!!!', str(printer_interface.cloud_printer_id))
    #         self.write_with_autoreplace(page)
    #     else:
    #         self.write_message('Lost connection to the printer. Please, try again.', show_time=2, response=400, redirect="/", button_text="", button_url="/", button_method="get", back_button_to="")

    # def change_cloud_printer_id(self):
    #     printer_id = self.post_data.get("printer_id", "")
    #     printer_interface = self.find_printer_interface(printer_id)
    #     if not printer_interface:
    #         self.write_message('Lost connection to the printer. Please try again', show_time=2, response=400, redirect="/", button_text="", button_url="/", button_method="get", back_button_to="")
    #     elif printer_interface.get_printer_state() == ('printing', 'paused', 'downloading'):
    #         self.write_message('Printer is currently busy. Please try again later', show_time=2, response=400, redirect="/", button_text="", button_url="/", button_method="get", back_button_to="")
    #     else:
    #         new_cloud_printer_id = self.post_data.get("new_cloud_printer_id", "")
    #         if new_cloud_printer_id:
    #             try:
    #                 new_cloud_printer_id = int(new_cloud_printer_id)
    #             except (ValueError, TypeError):
    #                 self.write_message('Invalid printer id', show_time=5, response=400, redirect="/", button_text="", button_url="/", button_method="get", back_button_to="")
    #             else:
    #                 printer_settings = printer_settings_and_id.load_settings(printer_id)
    #                 printer_settings['migrate_cloud_printer_id'] = new_cloud_printer_id
    #                 printer_interface.close()
    #                 printer_settings_and_id.save_settings(printer_id, printer_settings)
    #                 self.write_message('Printer migration request send. Check new printer id.', show_time=5, response=200, redirect="/show_printer_cloud_binding_page?printer_id=" + printer_id, button_text="", button_url="/", button_method="get", back_button_to="")
    #         #         return 
    #         # self.write_message('Error: migration failed.', show_time=5, response=400, redirect="/show_printer_cloud_binding_page?printer_id=" + printer_id, button_text="", button_url="/", button_method="get", back_button_to="")

    def close_wizard(self, silent=False):
        settings = config.get_settings()
        settings['wizard_on_start'] = False
        self.wizard_enabled = False
        config.Config.instance().save_settings(settings)
        if not silent:
            self.write_message('Wizard successfully closed. Loading main page...', show_time=1)
