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

import config
from base_sender import BaseSender

class Sender(BaseSender):

    AVERAGE_GCODE_TIME = 0.005 # 2000 gcodes a second
    CALLBACK_DELAY = 2
    DEFAULT_ZOFFSET = 2.0

    def __init__(self, parent, usb_info, profile):
        BaseSender.__init__(self, parent, usb_info, profile)
        self.print_thread = None
        self.total_gcodes = 0
        self.index = 0
        self.pause_flag = False
        self.cancel_flag = False
        self.z_offset = self.DEFAULT_ZOFFSET  
        self.buffer = collections.deque()
        self.print_now_buffer = collections.deque()
        self.verbose = config.get_settings()['verbose']

    def is_operational(self):
        return not self.stop_flag

    def is_paused(self):
        return self.pause_flag

    def load_gcodes(self, gcodes):
        self.logger.info("Virtual printer loading gcodes")
        self.buffer = self.preprocess_gcodes(gcodes)
        self.index = 0
        self.total_gcodes = len(self.buffer)
        self.print_thread = threading.Thread(target=self.printing)
        self.print_thread.start()

    def unbuffered_gcodes(self, gcodes):
        self.logger.info("Virtual printer unbuffered gcodes: %s" % gcodes)
        gcodes = self.preprocess_gcodes(gcodes)
        if gcodes:
            self.print_now_buffer.extend(gcodes)
        if not self.print_thread:
            self.print_thread = threading.Thread(target=self.printing)
            self.print_thread.start()
        #for gcode in self.preprocess_gcodes(gcodes):
        #    t = threading.Thread(target=self.delay_callback, args=("ok %s" % gcode, True), daemon=True) #switch here is you want success False
        #    t.start()

    def printing(self):
        self.logger.info("Print thread started")
        self.printing_flag = True
        while not self.stop_flag and not self.cancel_flag:
            line = None
            if self.print_now_buffer:
                try: 
                    line = self.print_now_buffer.popleft()
                except:
                    self.logger.exception()
            else:
                if self.pause_flag:
                    time.sleep(self.AVERAGE_GCODE_TIME)
                    continue
                try: 
                    line = self.buffer[self.index]
                    self.index += 1
                except IndexError:
                    self.logger.info("Last line index " + str(self.index))
                    break
            if line:
                line = line.split(";")[0].strip() # remove any comments
                if line:
                    if self.verbose:
                        self.logger.info("Virt in: %s" % line)
                    if "G31" in line:
                        if "Z" in line:
                            try:
                                self.z_offset = float(line.split("Z")[-1].strip().strip("\n"))
                            except:
                                pass
                            response = "ok %s" % line
                        else:
                            response = "Current reading 1000, threshold 500, trigger height %s, offsets X15.0 Y.0.0" %\
                                    self.z_offset
                    elif "prep_z_calib.g" in line:
                        response = "X:0.000 Y:0.000 Z:33.000 E:0.000 E0:0.0 E1:0.0 E2:0.0 E3:0.0 E4:0.0 E5:0.0 E6:0.0"
                        " E7:0.0 E8:0.0 Count 0 0 80000 Machine 0.000 0.000 100.000 Bed comp 0.000"
                    else:
                        response = "ok %s" % line
                    if self.verbose:
                        self.logger.info("Virt out: %s" % response)
                    self.execute_callback(response, True)
            time.sleep(self.AVERAGE_GCODE_TIME)
        self.printing_flag = False
        self.pause_flag = False
        self.cancel_flag = False
        self.buffer = collections.deque()
        self.logger.info("Print thread finished")

    def cancel(self):
        self.logger.info("Virtual printer cancel")
        self.cancel_flag = True
        self.pause_flag = False
        return True

    def pause(self):
        self.logger.info("Virtual printer pause")
        self.pause_flag = True

    def unpause(self):
        self.logger.info("Virtual printer unpause")
        self.pause_flag = False

    def get_percent(self):
        if self.total_gcodes:
            return int(self.get_current_line_number() / float(self.total_gcodes) * 100)
        else:
            return 0

    def get_temps(self):
        percent = self.get_percent()
        return [percent + 10, percent + 20 * 2]

    def get_current_line_number(self):
        return self.index

    def reset(self):
        self.logger.info("Virtual printer reset")

    def delay_callback(self, line, success):
        time.sleep(self.CALLBACK_DELAY)
        super().execute_callback(line, success)

    def release_ready_for_command(self):
        pass

    def start_temp_requesting(self):
        pass

    def stop_temp_requesting(self):
        pass

    def close(self):
        self.logger.info('Closing Virtual printer sender')
        self.stop_flag = True
        if self.print_thread:
            self.print_thread.join()

if __name__ == "__main__":
    pass
