# 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 zmq
from zmq.auth import load_certificate

import config
from http_client import ProtobufPrinterHTTPClient

class ZMQClient(ProtobufPrinterHTTPClient):

    BASE_TIMEOUT = 6
    URL = "tcp://zmq_%s:62234" % ProtobufPrinterHTTPClient.URL

    def connect(self):
        try:
            socket = self.parent.parent.zmq_context.socket(zmq.REQ)
            if config.get_settings()['protocol']['encryption']:
                public_key, secret_key = zmq.curve_keypair()
                socket.curve_secretkey = secret_key
                socket.curve_publickey = public_key
                server_key = load_certificate('printer_server.key')[0]
                socket.curve_serverkey = server_key
            socket.setsockopt(zmq.SNDTIMEO, self.BASE_TIMEOUT*1000)
            socket.setsockopt(zmq.RCVTIMEO, self.BASE_TIMEOUT*1000)
            socket.setsockopt(zmq.IMMEDIATE, True)
            socket.setsockopt(zmq.LINGER, 1000)
            self.logger.info('Connecting to ' + self.URL)
            socket.connect(self.URL)
        except zmq.ZMQError:
            self.parent.register_error(700, 'Unable to connect to server')
            self.logger.exception('Network error:')
        else:
            self.local_ip = self.parent.parent.local_ip
            if not self.mac:
                self.mac = self.get_mac_add_or_serial_number()
            return socket

    def pack_and_send(self, target, *payloads, **kwargs_payloads):
        target, packed_message = self.pack(target, *payloads, **kwargs_payloads)
        return self.send(target, packed_message)
         
    def send(self, target, data):
        while not self.parent.stop_flag:
            if not self.connection:
                self.connection = self.connect()
            try:
                self.connection.send(data)
                answer = self.connection.recv()
            except zmq.ZMQError: #TODO add better logging for timeouts
                self.parent.register_error(701, 'Network connection lost: ')
                self.logger.exception('Network error:')
                answer = None
                self.close()
                time.sleep(self.BASE_TIMEOUT / 2) # Some delay before next request
            else:
                return self.unpack(answer, target)

    def close(self):
        if self.connection:
            try:
                self.connection.disconnect(self.URL)
            except zmq.ZMQError:
                self.logger.exception('Disconnect failed')
            self.logger.info('Connection closed')
            self.connection.close()
            self.connection = None

if __name__ == "__main__":
    import logging
    import uuid
     
    IP = "192.168.1.6"
    SLEEP_TIME = 0.01 
    
    class FakeParent: 
        def __init__(self, parent=None):
            self.stop_flag = False
            self.parent = parent
            if not parent:
                self.zmq_context = zmq.Context()
                self.local_ip = "127.0.0.1"

        def register_error(self, code, message, is_blocking=False):
            print(code, message)
    
    app = FakeParent()
    printer_interface = FakeParent(app)
    ZMQClient.URL =  "tcp://%s:62234" % IP
    print('Connecting to:', ZMQClient.URL)
    zmq_client = ZMQClient(printer_interface)
    logging.basicConfig(level=logging.DEBUG)
    printer_token = uuid.uuid4().hex * 4
    command_args = [printer_token, {'state': 'ready', 'percent': 0, 'temps': [0, 0], 'target_temps': [0, 0], 'line_number': 0, 'coords': [0, 0, 0, 0]}, None]
    try:
        while True:
            start_time = time.monotonic()
            zmq_client.pack_and_send(ZMQClient.COMMAND, *command_args)
            latency = time.monotonic() - start_time
            print("Latency:", latency)
            time.sleep(SLEEP_TIME)
    finally:
        zmq_client.close()
    
    

