# 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 socket
import struct
import time

import base_scanner

class BambuLabDetector(base_scanner.Scanner):

    CAN_DETECT_SNR = True
    CAN_DETECT_VID_PID = True
    SOCKET_TIMEOUT = 1
    DETECTION_TIMEOUT = 11
    SOCKET_READ_SIZE = 1024
    SCANNER_NAME_IN_PROFILE = ["bambulab_scanner"]
    PORTS = [1900, 2021]

    def detect_printer_profile(self, data_str):
        device_info = {}
        for line in data_str.splitlines():
            line = line.strip()
            if line and ':' in line:
                key, value = line.split(":", 1)
                if key.strip() and value.strip():
                    device_info[key.strip()] = value.strip()
        snr = device_info.get("USN")
        printer_ip = device_info.get("Location")
        model_id = device_info.get("DevModel.bambu.com")
        if snr and printer_ip and model_id:
            for profile in self.profiles:
                machine_type_id = profile.get('network_detect', {}).get('machine_type_id')
                if machine_type_id and machine_type_id in model_id:
                    vid, pid = profile.get('vids_pids')[0]
                    return {
                        'SNR': str(snr),
                        'IP': str(printer_ip),
                        'VID': vid,
                        'PID': pid
                    }

    def detect(self, already_know_ip=None, non_default_port=None):
        self.logger.info("Start BambuLab detect")
        try:
            # Create a UDP socket and bind it so that we can listen to SSDP.
            sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            sock.bind(('0.0.0.0', 2021))
            # Join multicast group
            group = socket.inet_aton('239.255.255.250')
            mreq = struct.pack('4sL', group, socket.INADDR_ANY)
            sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
            sock.settimeout(self.SOCKET_TIMEOUT)
        except Exception as e:
            self.logger.exception("Exception in detector: " + str(e))
        else:
            start_time = time.monotonic()
            printers = {}
            while time.monotonic() - start_time < self.DETECTION_TIMEOUT:
                addr = None
                try:
                    data, addr = sock.recvfrom(self.SOCKET_READ_SIZE)
                    port = addr[1]
                    if port not in self.PORTS:
                        continue
                    data_str = data.decode('utf-8', errors='ignore')
                    device_info = self.detect_printer_profile(data_str)
                    if device_info:
                        printers[device_info["SNR"]] = device_info
                except socket.timeout:
                    pass
                except IndexError:
                    self.logger.error(f"Can't detect addr port. addr: {addr}")
                except Exception as e:
                    self.logger.exception("Exception in detector: " + str(e))
            self.discovered_printers = printers.values()
            return self.discovered_printers


if __name__ == "__main__":
    import pprint
    scr = BambuLabDetector(None)
    pprint.pprint(scr.detect())
