# 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 simplejson as json
import logging
import os
import time

import config
import paths
from . import wizard_former


class PageFormer:

    TYPE_SELECT_MESSAGE_WAIT = 10

    def __init__(self, server):
        self.logger = logging.getLogger(__name__)
        self.server = server
        self.show_your_account_button = True
        self.wizard_enabled = config.get_settings()['wizard_on_start']
        self.wizard_former = wizard_former.WizardFormer(self)

    def set_wizard_stage(self, stage_name):
        self.wizard_former.current_stage = stage_name

    def form_confirm_page(self, body, confirm_path):
        page = self.server.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):
        return self.form_confirm_page('<h3>Quit</h3>Are you sure you want to exit 3DPrinterOS Client?'
                                      '<br />All your active prints will be interrupted.',
                                      '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 place_scripts_on_page(self, page_content, *scripts):
        scripts_content = ''
        for script in scripts:
            scripts_content += '<script>' + script + '</script>'
        return page_content.replace('!!!SCRIPTS!!!', scripts_content)

    def get_script_to_refresh_element(self, element_id, path_tail, refresh_period=0.3, error_message='', error_count=5,
                                      ajax_timeout=10):
        return self.server.web_content_files['templates']['scripts']['refresh_script.js'] \
            .replace('!!!AJAX_TIMEOUT!!!', str(ajax_timeout * 1000)) \
            .replace('!!!ELEMENT_ID!!!', element_id) \
            .replace('!!!URL!!!', '/ajax/' + path_tail) \
            .replace('!!!ERROR_COUNT!!!', str(error_count)) \
            .replace('!!!REFRESH_PERIOD!!!', str(int(refresh_period * 1000))) \
            .replace('!!!ERROR_MESSAGE!!!', '<font color="black">' + error_message + '</font>')

    def form_main_page(self):
        update_complete_path = os.path.join(paths.CURRENT_SETTINGS_FOLDER, 'update_complete')
        if os.path.exists(update_complete_path):
            os.remove(update_complete_path)
            return self.server.web_content_files['templates']['pages']['update_complete.html']
        if self.server.app.stop_flag:
            return self.form_closing_page()
        elif not self.server.app.user_login.user_token:
            name = 'login.html'
        elif self.server.app.rights_checker.waiting:
            return self.server.web_content_files['templates']['pages']['groups_warning.html']
        elif self.server.app.conveyor_kill_waiter.waiting:
            return self.server.web_content_files['templates']['pages']['conveyor_warning.html']
        else:
            name = 'main_loop_form.html'
        page = self.server.web_content_files['templates']['pages'][name]
        if self.show_your_account_button:
            your_account_button = self.server.web_content_files['templates']['buttons']['your_account_button.html']
        else:
            your_account_button = ''
        page = page.replace('!!!YOUR_ACCOUNT_BUTTON!!!', your_account_button)
        page = page.replace('!!!HTTPS_WARNING!!!', self.form_http_warning())
        login = self.server.app.user_login.login
        if login:
            if self.wizard_enabled:
                return self.wizard_former.get_current_page()
            page = page.replace('!!!LOGIN!!!', login)
            page = page.replace('!!!HOST_MAC!!!', self.server.app.user_login.mac)
            page = self.place_scripts_on_main_page(page)
            page = self.place_virtual_printer_button(page)
        return page

    def form_http_warning(self):
        message = ''
        if not self.server.HTTPS:
            https_port = config.get_settings()['web_interface']['port_https']
            if https_port:
                message = '<div id="http_secure_message">For security reasons we strongly recommend to use HTTPS url to access the cloud client interface.' \
                        ' <a href="https://!!!HOST!!!:%d/">Click here to switch to HTTPS</a>.</div>' % https_port
        return message

    def place_virtual_printer_button(self, page):
        virtual_printer_button = self.server.web_content_files['templates']['buttons']['virtual_printer_button.html']
        if self.server.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('!!!VIRTUAL_PRINTER_PATH!!!', 'toggle_virtual_printer')
        return page.replace('!!!VIRTUAL_PRINTER_BUTTON!!!', virtual_printer_button)

    def place_cloudsync_buttons(self, page):
        if getattr(getattr(self.server.app, 'cloud_sync_controller', None), 'cloud_sync_process', None):
            action = 'Disable CloudSync'
            open_folder_button = self.server.web_content_files['templates']['buttons']['open_cloudsync_folder.html']
        else:
            action = 'Enable CloudSync'
            open_folder_button = ''
        cloud_sync_buttons = self.server.web_content_files['templates']['buttons']['toggle_cloudsync_button.html']
        cloud_sync_buttons = cloud_sync_buttons.replace('!!!ACTION!!!', action)
        page = page.replace('!!!CLOUD_SYNC_BUTTONS!!!', cloud_sync_buttons + open_folder_button)
        return page

    def place_scripts_on_main_page(self, main_page_content):
        scripts = self.get_script_to_refresh_element('printers_info', 'get_printers_info',
                                                     error_message='No connection to 3DPrinterOS Client<br />'
                                                                   'Most likely it was closed'), \
                  self.get_script_to_refresh_element('get_updates_button', 'get_updates_button', refresh_period=3), \
                  self.server.web_content_files['templates']['scripts']['mousedown_script.js']
        return self.place_scripts_on_page(main_page_content, *scripts)

    def place_scripts_on_closing_page(self, page_content):
        refresh_script = self.get_script_to_refresh_element('closing_client_info', 'get_closing_client_info',
                                                            error_message='3DPrinterOS Client has stopped. Goodbye.',
                                                            error_count=1, ajax_timeout=1)
        return self.place_scripts_on_page(page_content, refresh_script)

    def place_scripts_on_logs_page(self, logs_page_content):
        script = self.get_script_to_refresh_element('log_tail', 'write_log_tail',
                                                    error_message='3DPrinterOS Client logging has stopped.')
        return self.place_scripts_on_page(logs_page_content, script)

    def get_printers_info(self, rename_button=True, type_button=True, groups_button=True, local_mode_button=True):
        printers_list = []
        printers_string = ''
        pairing_warning = ''
        for printer_interface in self.server.app.printer_interfaces:
            printer_string = self.get_printer_profile_string(printer_interface, rename_button, type_button,
                                                             groups_button, local_mode_button)
            printer = getattr(printer_interface, 'printer', None)
            if printer:
                if getattr(printer, 'pairing_needed', False):
                    pairing_warning =  self.server.web_content_files['templates']['pages']['pairing_needed.html']
            printers_list.append(printer_string)
        printers_string += pairing_warning
        printers_string += ''.join(["<p>" + x + "</p>" for x in printers_list])
        if not printers_string:
            printers_string = '<p><b>No printers detected</b>\
                <br />Please do a power cycle for printers\
                <br />and then ensure your printers are connected\
                <br />to power outlet and usb cord</p>'
        return printers_string

    def get_printer_snr(self, printer_interface):
        snr = printer_interface.usb_info['SNR']
        if not snr:
            snr = ''
        return snr

    def get_printer_profile(self, printer_interface):
        if not getattr(printer_interface, 'printer_profile', False):
            return {'alias': "", 'name': 'Awaiting profile %s:%s' % (printer_interface.usb_info['PID'],
                                                                        printer_interface.usb_info['VID'])}
        else:
            return printer_interface.printer_profile

    def get_printer_profile_string(self, printer_interface, rename_button=True, type_button=True,
                                   groups_button=True, local_mode_button=True):
        snr = self.get_printer_snr(printer_interface)
        profile = self.get_printer_profile(printer_interface)
        if printer_interface.printer_name and rename_button:
            printer_rename_link = self.server.web_content_files['templates']['buttons']['manage_printer_button.html'] \
                .replace('class="small_button', 'title="Press here to rename this printer" class="submit_link') \
                .replace('!!!PATH!!!', 'rename_printer') \
                .replace('!!!ACTION_NAME!!!', printer_interface.printer_name) \
                .replace('!!!USB_INFO!!!', str(printer_interface.usb_info))
        else:
            printer_rename_link = ''
        if profile.get('ID_CLONE_WARNING'):
            id_clone_string = '<span title=" This printer have same USB id as another printer connection to this' \
                              'machine. Printer is working in compatibility mode.">' \
                              '<font color="maroon">&#9888</font><span> '
        else:
            id_clone_string = ''
        printer_string = '<b>' + printer_rename_link + '<br />'
        printer_string += id_clone_string + profile['name']\
                         + ' </b><font title="Serial number">' + str(snr) + '</font>'
        if not getattr(printer_interface, "printer_token", None):
            if time.time() - printer_interface.creation_time < self.TYPE_SELECT_MESSAGE_WAIT:
                printer_string += '<br>Waiting type selection from server'
            else:
                printer_string += '<br>Please select your printer type'
        printer_report = printer_interface.state_report()
        if printer_report and printer_report['state']:
            error = self.get_printer_error_string(printer_interface)
            if error:
                error_message = '<br /><font color="maroon">' + error + '</font>'
            else:
                error_message = ''
            printer_string += ' - ' + self.get_printer_state_string(printer_interface) \
                                    + '<br />' + self.get_printer_temps_string(printer_report) \
                                    + error_message + '<br />'
            if type_button:
                printer_string += self.get_printer_type_select_button(printer_interface)
            if groups_button:
                printer_string += self.get_printer_groups_select_button(printer_interface)
        if local_mode_button:
            printer_string += self.get_printer_local_mode_button(printer_interface)
        return printer_string + self.get_manage_printer_button(printer_interface) + '<hr width="320px">'

    def get_printer_error_string(self, printer_interface):
        error_dict = printer_interface.get_last_error_to_display()
        if error_dict:
            return 'Error ' + str(error_dict['code']) + ': ' + error_dict['message']

    def get_printer_state_string(self, printer_interface):
        printer_report = printer_interface.state_report()
        state = printer_report['state']
        percent = printer_report.get('percent')
        if state == 'error':
            color = 'red'
        elif state in ('connecting', "closing"):
            color = 'purple'
        elif state in ('ready', "local_mode"):
            color = 'green'
        elif state == 'local_mode':
            color = 'brown'
        elif state == 'printing':
            color = 'blue'
        elif state == 'paused':
            color = 'orange'
        elif state == 'downloading':
            color = 'lightblue'
        else:
            color = 'red'
            self.logger.error("State unknown to web_interface: '%s'" % str(state))
        if percent is not None and state in ('printing', 'paused', 'downloading'):
            progress = ' | %d%% %s' % (percent, self.get_printing_cancel_button(printer_interface))
        else:
            progress = ''
        return '<font color="' + color + '">' + state + progress + '</font> '

    def get_printer_temps_string(self, printer_report):
        temps = printer_report.get('temps')
        target_temps = printer_report.get('target_temps')
        temps_string = ''
        if temps and target_temps:
            temps_string =  'First Tool: %s/%s | Heated Bed: %s/%s' %\
                            (temps[1], target_temps[1], temps[0], target_temps[0])
            if len(temps) >= 3 and len(target_temps) >= 3:
                temps_string = temps_string.replace('| H', '| Second Tool: %s/%s | H' % (temps[2], target_temps[2]))
        return temps_string

    def get_printer_type_select_button(self, printer_interface):
        profile = getattr(printer_interface, 'printer_profile', None)
        if not profile:
            if printer_interface.show_printer_type_selector:
                button_name = 'Select printer type'
                printer_type_path = 'select_printer_type'
            else:
                button_name = 'Please wait...'
                printer_type_path = ''
        else:
            button_name = 'Reset printer type'
            printer_type_path = 'request_printer_type_reset'
        return self.server.web_content_files['templates']['buttons']['manage_printer_button.html']\
            .replace('!!!ACTION_NAME!!!', button_name)\
            .replace('!!!USB_INFO!!!', str(printer_interface.usb_info))\
            .replace('!!!PATH!!!', printer_type_path)

    def get_printer_groups_select_button(self, printer_interface):
        printer_groups_select_path = 'select_printer_groups'
        return self.server.web_content_files['templates']['buttons']['manage_printer_button.html']\
            .replace('!!!PATH!!!', printer_groups_select_path) \
            .replace('!!!ACTION_NAME!!!', 'Manage workgroups') \
            .replace('!!!USB_INFO!!!', str(printer_interface.usb_info))

    def get_printing_cancel_button(self, printer_interface):
        printing_cancel_path = 'cancel_printing'
        return self.server.web_content_files['templates']['buttons']['manage_printer_button.html']\
            .replace('!!!PATH!!!', printing_cancel_path) \
            .replace('!!!ACTION_NAME!!!', 'Cancel') \
            .replace('!!!USB_INFO!!!', str(printer_interface.usb_info)) \
            .replace("style='display:inline;'", 'style="display:inline; width: 60px"')

    def get_printer_local_mode_button(self, printer_interface):
        path = 'toggle_local_mode'
        if printer_interface.local_mode:
            action_name = 'Disable local mode'
            button_color = 'FF3300'
        else:
            action_name = 'Enable local mode'
            button_color = '009999'
        return self.server.web_content_files['templates']['buttons']['manage_printer_button.html']\
            .replace('!!!PATH!!!', path) \
            .replace('!!!ACTION_NAME!!!', action_name) \
            .replace('!!!USB_INFO!!!', str(printer_interface.usb_info))\
            .replace("style='display:inline;'", "style='display:inline;background:#%s'" % button_color)

    def get_manage_printer_button(self, printer_interface):
        manage_printer_path = 'manage_printer'
        return self.server.web_content_files['templates']['buttons']['manage_printer_button.html']\
            .replace('!!!PATH!!!', manage_printer_path)\
            .replace('!!!ACTION_NAME!!!', 'Manage printer')\
            .replace('!!!USB_INFO!!!', str(printer_interface.usb_info))

    def form_manage_printer_page(self, usb_info_string):
        printer_interface = self.server.find_printer_interface(usb_info_string)
        if printer_interface:
            page = self.server.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.server.find_printer_interface(usb_info_string)
        if printer_interface:
            page = self.server.web_content_files['templates']['pages']['rename_printer.html']
            return page.replace('!!!PATH!!!', 'set_new_printer_name') \
                .replace('!!!USB_INFO!!!', 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.server.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 />'
            page = self.server.web_content_files['templates']['pages']['select_printer_groups.html']
            page = page.replace('!!!PATH!!!', 'confirm_printer_groups/' + usb_info_string)
            if groups_checkboxes:
                return page.replace('!!!CHECKBOXES!!!', groups_checkboxes)

    def form_back_button(self, back_path):
        return self.server.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.server.find_printer_interface(usb_info_string)
        if printer_interface:
            selector = self.form_printer_type_selector(printer_interface.possible_printer_types)
            page = self.server.web_content_files['templates']['pages']['choose_printer_type.html']
            return page.replace('!!!TYPE_SELECT!!!', selector) \
                                .replace('!!!PATH!!!', 'choose_printer_type')\
                                .replace('!!!USB_INFO!!!', usb_info_string)

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

    def form_connect_to_network_printer_page(self):
        possible_printer_types = []
        for profile in self.server.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)
        page = self.server.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.server.find_printer_interface(usb_info_string)
        profile = getattr(printer_interface, 'printer_profile', None)
        if profile:
            return self.server.web_content_files['templates']['pages']['request_printer_type_reset.html']\
                .replace('!!!PRINTER_TYPE!!!', profile['name'])\
                .replace('!!!PATH!!!', 'reset_printer_type')\
                .replace('!!!USB_INFO!!!', usb_info_string)

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

    def form_camera_mode_selector(self):
        module_selector_html = ''
        if hasattr(self.server.app, 'camera_controller'):
            modules = self.server.app.camera_controller.CAMERA_MODULES
            for module in list(modules.keys()):
                if module == self.server.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.server.web_content_files['templates']['pages']['start_hd_camera.html']
        options = ''
        for printer_interface in self.server.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.server.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.server.web_content_files['templates']['pages']['update_software.html']

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

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

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

    def form_json_active_printers(self, include_ready=True):
        printers_info = []
        for printer_interface in self.server.app.printer_interfaces:
            state_report = printer_interface.state_report()
            if state_report:
                if not include_ready and state_report['state'] == 'ready':
                    continue
                else:
                    printers_info.append({'profile': self.get_printer_profile(printer_interface),
                                          'state_report': state_report,
                                          'temps': self.get_printer_temps_string(state_report),
                                          'error': self.get_printer_error_string(printer_interface)})
        return json.dumps(printers_info)

    def form_json_confirm_identical_printers(self):
        usb_info_list = []
        for printer_interface in self.server.app.printer_interfaces:
            usb_info_list.append(printer_interface.usb_info)
        for usb_info in usb_info_list:
            if usb_info_list.count(usb_info) > 1:
                return json.dumps(True)
        return json.dumps(False)

    def form_json_updates_available(self):
        updates_available = getattr(getattr(self.server.app, 'updater', None), 'update_flag', None)
        return json.dumps(updates_available)

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

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

    def form_detect_network_clients_page(self):
        detected_clients = self.server.app.client_detector.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.server.web_content_files['templates']['pages']['detect_clients.html']
            return page.replace('!!!SELECTOR!!!', html)

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

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

    def form_manage_settings_page(self):
        if config.get_settings()['update']['enabled']:
            action_name = 'Disable update'
        else:
            action_name = 'Enable update'
        update_section = self.server.web_content_files['templates']['buttons']['toggle_button.html']\
            .replace('!!!PATH!!!', 'settings/toggle_update')\
            .replace('!!!ACTION!!!', action_name)
        if config.get_settings()['update']['enabled']:
            if config.get_settings()['update']['auto_update_enabled']:
                action_name = 'Disable auto update'
            else:
                action_name = 'Enable auto update'
            update_section += self.server.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.server.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(config.get_settings()['update']['check_pause']))
        page = self.server.web_content_files['templates']['pages']['manage_settings.html']\
            .replace('!!!UPDATE_SECTION!!!', update_section)
        page = self.place_cloudsync_buttons(page)
        return page

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