# 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 os
import os.path

import ssl
import time
import logging
import threading
import http.server
import subprocess
from socketserver import ThreadingMixIn

import config
from . import page_former
from . import web_interface_handler
import log
import paths
from awaitable import Awaitable


WEB_CONTENT_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'web_content_files')


def load_web_content(path=WEB_CONTENT_FOLDER):
    binary_files_extensions = ('jpg', 'png', 'ico')
    web_content = {}
    for name in os.listdir(path):
        file_path = os.path.join(path, name)
        if os.path.isdir(file_path):
            web_content[name] = load_web_content(file_path)
        else:
            read_mode = 'r'
            if name.split(".")[-1] in binary_files_extensions:
                read_mode += 'b'
            try:
                with open(file_path, read_mode) as f:
                    web_content[name] = f.read()
            except Exception as e:
                logger = logging.getLogger(__name__)
                error = 'Error while reading file "' + name + '": ' + str(e)
                logger.error(error)
                raise RuntimeError(error)
    return web_content


web_content = load_web_content()


class ThreadedHTTPServer(ThreadingMixIn, http.server.HTTPServer):
    """ This class allows to handle requests in separated threads.
        No further content needed, don't touch this. """


class WebInterface(threading.Thread, Awaitable):

    PORT_SETTING_NAME = "port"
    NAME = "web interface"

    def __init__(self, app):
        self.logger = logging.getLogger(__name__)
        self.app = app
        self.server = None
        self.web_content_files = {}
        self.thread_https = None
        self.port = config.get_settings()['web_interface'][self.PORT_SETTING_NAME]
        if config.get_settings()['remote_control']['web_server']:
            self.ip = config.REMOTE_IP
        else:
            self.ip = config.LOCAL_IP
        threading.Thread.__init__(self, name=self.NAME)
        Awaitable.__init__(self, app)

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

    @log.log_exception
    def run(self):
        self.logger.info("Starting web server...")
        try:
            server = ThreadedHTTPServer((self.ip, self.port), web_interface_handler.WebInterfaceHandler)
            server.daemon_threads = True
            server.HTTPS = False
        except Exception:
            self.logger.exception("Error: unable to start Web Interface Server")
        else:
            self.run_server(server)

    def run_server(self, server):
        self.logger.info("...web server started on port: %s" % self.port)
        server.app = self.app
        server.web_content_files = web_content
        server.page_former = page_former.PageFormer(server)
        server.find_printer_interface = self.find_printer_interface
        self.server = server
        server.serve_forever()
        if server.socket is not None:
            server.server_close()
        self.logger.info("Web server stopped on port: %s" % self.port)

    def check_function(self):
        return bool(self.server)

    def close(self, second_attempt=False):
        if self.server:
            self.server.shutdown()
        elif not second_attempt:
            time.sleep(0.5)
            self.close(second_attempt=True)


class HTTPSWebInterface(WebInterface):

    PORT_SETTING_NAME = "port_https"
    NAME = "HTTPS web interface"

    def run_server(self, server):
        self.load_ssl_certificates(server)
        server.HTTPS = True
        super().run_server(server)

    def load_ssl_certificates(self, server):
        keyfile = os.path.join(paths.CURRENT_SETTINGS_FOLDER, 'private_key.pem')
        certfile = os.path.join(paths.CURRENT_SETTINGS_FOLDER, 'certificate.pem')
        if not os.path.isfile(keyfile) or not os.path.isfile(certfile):
            self.generate_ssl_certificates(keyfile, certfile)
        ssl_version = ssl.PROTOCOL_SSLv23
        if hasattr(ssl, 'PROTOCOL_TLSv1_2'):
            ssl_version = ssl.PROTOCOL_TLSv1_2
        server.socket = ssl.wrap_socket(server.socket, ssl_version=ssl_version, keyfile=keyfile,
                                        certfile=certfile, ca_certs=None, server_side=True,
                                        ciphers='HIGH:!aNULL:!eNULL:!SSLv2:!SSLv3:!MD5:!RC4:!DSS:!3DES:@STRENGTH')

    def generate_ssl_certificates(self, keyfile, certfile):
        cmd = ["openssl", "req", "-x509", "-newkey", "rsa:2048", "-keyout", keyfile, "-out", certfile,
               "-days", "10000", "-nodes", "-subj",
               "/C=US/ST=California/L=San Francisco/O=3D Control Systems/OU=3DPrinterOS/CN=3dprinteros.com"]
        call = subprocess.run(cmd, capture_output=True)
        self.logger.info('OpenSSL stdout: %s' % call.stdout)
        if call.returncode:
            self.logger.error('Some problem with auto certificates generating. '
                              'Please refer to how-to-generate-certificates.txt and generate certs manually.')
            self.logger.info('OpenSSL stderr: %s' % call.stderr)
            raise RuntimeError("OpenSSL generation failed with code: %d" % call.returncode)
