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


class EmulatedSerialConnection:

    DEFAULT_EXEC_TIMES = {"G0" : 0.1, "G28" : 2}
    IDLE_SLEEP = 0.01

    def __init__(self, port_name="/dev/null", baudrate=115200, timeout=1, start_dtr=None):
        self.logger = logging.getLogger(__name__)
        self.port_name = port_name
        self.baudrate = baudrate
        self.timeout = timeout
        self.verbose = config.get_settings()['verbose']
        self.in_buffer = collections.deque()
        self.out_buffer = collections.deque(['start'])
        self.port = True
        self.exec_thread = threading.Thread(target=self.gcode_execution_loop)
        self.exec_thread.start()

    def gcode_execution_loop(self):
        while self.port:
            if self.in_buffer:
                gcode = self.in_buffer.popleft()
                for key in self.DEFAULT_EXEC_TIMES:
                    if key and gcode.startswith(key):
                        time.sleep(self.DEFAULT_EXEC_TIMES[key])
                        break
                if 'M105' in gcode:
                    self.out_buffer.append("ok T:200 /200 B:70 /70")
                else:
                    self.out_buffer.append("ok " + gcode)
            else:
                time.sleep(self.IDLE_SLEEP)

    def recv(self, size=None):
        if self.out_buffer:
            if size:
                data = self.out_buffer[0][:1:]
                self.out_buffer[0] = self.out_buffer[0][1:]
            else:
                data = self.out_buffer.popleft()
            if self.verbose:
                self.logger.info('RECV: ' + str(data))
            return data
        elif self.out_buffer == None:
            self.logger.warning('Receive called on closed port')
            time.sleep(1)
        else:
            time.sleep(self.IDLE_SLEEP)
        return b""

    def prepare_data(self, data):
        return data.strip() + "\n"

    def send(self, data, raw=False):
        if not raw:
            data = self.prepare_data(data)
        if self.in_buffer != None:
            if self.verbose:
                self.logger.info('SEND: ' + str(data))
            self.in_buffer.append(data)
            return True
        else:
            self.logger.warning('Send called on closed port')
            return False

    def reset(self):
        self.logger.info('Port reset')

    def close(self):
        self.logger.info('Port closed')
        self.in_buffer = None
        self.out_buffer = None
        self.port = False
