# 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 typing

import config
from base_sender import BaseSender

class Sender(BaseSender):

    MIN_AVERAGE_GCODE_TIME = 0.005 # 200 gcodes a second
    CALLBACK_DELAY = 2

    def __init__(self, parent: typing.Any, usb_info: dict, profile: dict):
        BaseSender.__init__(self, parent, usb_info, profile)
        self.print_thread = None
        self.printing_flag = False
        self.total_gcodes = 0
        self.pause_flag = False
        self.cancel_flag = False
        self.verbose = config.get_settings()['verbose']
        self.buffer = collections.deque()
        self.send_now_buffer = collections.deque()

    def is_operational(self) -> bool:
        #return False
        return not self.stop_flag

    def is_printing(self) -> bool:
        return self.printing_flag

    def is_paused(self) -> bool:
        return self.pause_flag

    def load_gcodes(self, gcodes: typing.AnyStr) -> None:
        self.logger.info("Virtual printer loading gcodes")
        self.buffer.extend(gcodes)
        self.total_gcodes = len(gcodes)
        self.print_thread = threading.Thread(target=self._printing, daemon=True)
        self.print_thread.start()

    def unbuffered_gcodes(self, gcodes: typing.AnyStr) -> None:
        self.logger.info("Virtual printer unbuffered gcodes: %s" % gcodes)
        self.send_now_buffer.extend(self.preprocess_gcodes(gcodes))
        if not self.print_thread or not self.print_thread.is_alive():
            self.print_thread = threading.Thread(target=self._printing, daemon=True)
            self.print_thread.start()

    def emulate_send_line_and_resp(self, line: str) -> None:
        line = line.split(b";")[0].strip() # remove any comments
        if self.verbose:
            self.logger.info('SEND: ' + str(line))
        if line:
            response = "ok %s" % line
            self.execute_callback(response, True)
            if self.verbose:
                self.logger.info('RECV: ' + str(response))

    def _printing(self) -> None:
        self.printing_flag = True
        self.pause_flag = False
        self.current_line_number = 0
        # try to print any gcode in 100 seconds, but not faster then 200 gcodes per second
        if self.total_gcodes:
            delay_per_gcode = max(100.0 / self.total_gcodes, self.MIN_AVERAGE_GCODE_TIME)
        else:
            delay_per_gcode = 0
        self.logger.info(f'Delay per gcode: {delay_per_gcode}')
        while not self.stop_flag and\
              not self.cancel_flag and\
              not self.current_line_number > self.total_gcodes: 
            if self.send_now_buffer:
                line = self.send_now_buffer.popleft()
                self.emulate_send_line_and_resp(line)
                continue
            if not self.pause_flag:
                try:
                    line = self.buffer[self.current_line_number]
                except IndexError:
                    break
                else:
                    self.emulate_send_line_and_resp(line)
                    self.current_line_number += 1
            time.sleep(delay_per_gcode)
        self.printing_flag = False
        self.cancel_flag = False
        self.pause_flag = False
        self.buffer.clear()

    def cancel(self) -> bool:
        self.logger.info("Virtual printer cancel")
        self.pause_flag = False
        self.cancel_flag = True
        time.sleep(self.MIN_AVERAGE_GCODE_TIME*2)
        return True

    def pause(self) -> bool:
        self.logger.info("Virtual printer pause")
        self.pause_flag = True
        return True

    def unpause(self) -> bool:
        self.logger.info("Virtual printer unpause")
        self.pause_flag = False
        return True

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

    def get_temps(self) -> typing.List[float]:
        percent = self.get_percent()
        return [percent + 10.0, percent + 20 * 2.0]

    def get_current_line_number(self) -> int:
        return self.current_line_number

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

    def _delay_callback(self, line: typing.AnyStr, success: bool) -> None:
        time.sleep(self.CALLBACK_DELAY)
        super().execute_callback(line, success)

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