# 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 time
import logging

import version
import config
try:
    import printeros_pb2
    from google.protobuf.message import DecodeError
except ImportError:
    logging.getLogger().error("No google module for protobuf. Protobuf protocol will be unstable.")
    DecodeError = Exception


# This is adapter for old for old API of http_client
# TODO create new server protocol API and rewrite printer_interface
class ProtobufProtocol:

    def camera_name_to_protobuf(self, camera_name):
        camera_name = camera_name.replace(" ", "_")
        return printeros_pb2.CameraType.Value(camera_name)

    def camera_name_from_protobuf(self, camera_key):
        protobuf_value = printeros_pb2.CameraType.Name(camera_key)
        return protobuf_value.replace("_", " ")

    def remove_duplicate_errors(self, errors):
        clear_errors = []
        for error in errors:
            if not error in clear_errors:
                clear_errors.append(error)
        return clear_errors

    def pack_printer_login(self, *args, **kwargs):
        message = printeros_pb2.PrinterLoginRequest()
        message.user_token = args[0]
        usb_info = args[1]
        for key in usb_info:
            value = usb_info[key]
            if not value:
                value = ""
            setattr(message.printer, key, value)
        message.client_version = version.full_version_string()
        message.date_time = time.ctime()
        if "camera_name" in kwargs:
            camera_name = kwargs['camera_name']
        else:
            camera_name = config.get_app().camera_controller.get_current_camera_name()
        message.camera = self.camera_name_to_protobuf(camera_name)
        wrap_message = printeros_pb2.ClientMessageWrap()
        wrap_message.printer_login_request.CopyFrom(message)
        return wrap_message.SerializeToString()

    def pack_command_request(self, *args, **kwargs):
        message = printeros_pb2.CommandRequest()
        message.printer_token = args[0]
        dict_report = args[1]
        ack = args[2]
        if "select_groups" in kwargs:
            for group in kwargs["select_groups"]:
                message.select_groups.append(group)
            del kwargs["select_groups"]
        if "errors" in kwargs:
            errors = self.remove_duplicate_errors(kwargs["errors"])
            for error in errors:
                error_message = message.errors.add()
                error_message.code = error['code']
                error_message.message = error['message']
            del kwargs["errors"]
        if ack:
            message.ack.number = ack['number']
            message.ack.result = ack['result']
        for key in dict_report:
            value = dict_report[key]
            if key == "state":
                if value in printeros_pb2.CommandRequest.PrinterState.keys():
                    message.state = printeros_pb2.CommandRequest.PrinterState.Value(value)
                else:
                    self.logger.warning("Unknown printer state: " + str(key))
                    message.state = printeros_pb2.PrinterState.error
            elif key == "temps":
                message.report.bed_temp = value[0]
                ext_temps = value[1:]
                for temp in ext_temps:
                    message.report.ext_temps.append(temp)
            elif key == "target_temps":
                message.report.bed_t_temp = value[0]
                ext_target_temps = value[1:]
                for temp in ext_target_temps:
                    message.report.ext_t_temps.append(temp)
            elif key == "coords":
                for coord in dict_report["coords"]:
                    message.report.coords.append(coord)
            else:
                setattr(message.report, key, value)
        wrap_message = printeros_pb2.ClientMessageWrap()
        wrap_message.command_request.CopyFrom(message)
        return wrap_message.SerializeToString()

    def unpack_command(self, message):
        answer_dict = {}
        wrap = printeros_pb2.ServerMessageWrap()
        try:
            wrap.ParseFromString(message)
            command = wrap.server_command
        except (DecodeError, TypeError, AttributeError):
            logging.getLogger(__name__).exception("Can't parser servers command: " + str(message))
        else:
            command_string = command.command_string
            error = command.error
            if command_string:
                answer_dict['command'] = command.command_string
                answer_dict['number'] = command.number
                answer_dict['payload'] = command.payload
                answer_dict['link'] = command.link
                answer_dict['zip'] = command.zip
            if error:
                dict_error = {}
                dict_error['code'] = error.code
                dict_error['message'] = error.message
                answer_dict['error'] = dict_error
        return answer_dict

    def unpack_printer_login(self, message):
        answer_dict = {}
        wrap = printeros_pb2.ServerMessageWrap()
        try:
            wrap = printeros_pb2.ServerMessageWrap(message)
            printer_login = wrap.printer_login
        except (DecodeError, TypeError, AttributeError):
            logging.getLogger(__name__).exception("Can't parser servers printer_login: " + str(message))
        else:
            printer_token = printer_login.printer_token
            error = printer_login.error
            if printer_token:
                answer_dict['printer_token'] = printer_token
                answer_dict['printer_profile'] = printer_login.printer_profile
                answer_dict['name'] = printer_login.name
                answer_dict['operational_timeout'] = printer_login.operational_timeout
                answer_dict['groups'] = list(printer_login.groups)
            if error:
                dict_error = {}
                dict_error['code'] = error.code
                dict_error['message'] = error.message
                answer_dict['error'] = dict_error
        return answer_dict


if __name__ == "__main__":
    #FIXME this test is for older version of packaging without wrap message
    #TODO create a real test from this code
    logging.basicConfig()
    protocol = ProtobufProtocol()
    login_args = ['5bbb32b083e6f8.75484551', {'VID': 'ZZZZ', 'PID': 'ZZZZ', 'SNR': '0', 'COM': None}]
    print("Packed login:", protocol.pack_printer_login(*login_args, camera_name="Disable_camera"))
    print("-------")

    command_args = ['cU3j09RZTKU1k38r47o2OBxtMqSqaEqS54p6b7c3hQ8D1jOfBSF7qG1gIT13YFWSpZILVjAuvVsNfoXvVOs0dfHdOIar4m0dnnOrdZHovZwSnw0FszaOOa1vzaegA9fY', {'state': 'ready', 'percent': 0, 'temps': [0, 0], 'target_temps': [0, 0], 'line_number': 0, 'coords': [0, 0, 0, 0]}, None]
    iterations_number = 1000*1000
    start = time.monotonic()
    for i in range(1, iterations_number):
        packed_command = protocol.pack_command_request(*command_args)
    print("Pack %s times time: %fs" % (iterations_number, (time.monotonic() - start)))
    print("Packed command:", packed_command)
    print("-------")
    iterations_number = 1000*1000
    start = time.monotonic()
    for i in range(1, iterations_number):
        server_command = printeros_pb2.ServerCommand()
        server_command.command_string = "gcodes"
        server_command.payload = "G28"
        server_command.number = 11111111
        server_command.link = False
        server_command.zip = False
        server_command.error.code = 222
        server_command.error.message = "FOOBAR"
        string_command = server_command.SerializeToString()
    print("Pack %s times time: %fs" % (iterations_number, (time.monotonic() - start)))
    unpacked_command = protocol.unpack_command(string_command)
    print("Unpacked command:", unpacked_command)
    print(string_command)
    print("-------")

    printer_login = printeros_pb2.PrinterLoginAnswer()
    printer_login.printer_token = "sdafasdf2134123fadfsdf234234"
    printer_login.printer_profile = '{"something": "likejson"}'
    printer_login.name = "THE PRINTER" 
    printer_login.operational_timeout = 10
    printer_login.groups.extend({'Evil copr', 'evil corp too', 'eviliest worst corp', 'beloved university', 'I dont know how I got here'})
    printer_login.error.code = 333
    printer_login.error.message = "You are" 
    string_pritner_login = printer_login.SerializeToString()
    print("Unpacked printer login:", protocol.unpack_printer_login(string_pritner_login))
    print(string_pritner_login)


