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

import threaded_sender


# TODO: Need implement validate Firmware Version
class SDCardPrintThread(threaded_sender.PrintThread):
    START_WRITE_FILE_GCODE = "M28 1.gco"
    END_WRITE_FILE_GCODE = "M29 1.gco"
    START_PRINT_FROM_FILE = "M623 1.gco"

    def __init__(self, sender):
        self.print_thread_flag = True
        super(SDCardPrintThread, self).__init__(sender)

    def load_gcodes(self, gcodes):
        self.logger.info("Starting gcodes loading...")
        gcodes = self.sender.preprocess_gcodes(gcodes)
        self.total_gcodes = len(gcodes)
        self.lines_sent = 0
        #gcodes.insert(0, self.GO_TO_LINE_N_GCODE)
        self.buffer = self.sender.buffer_class(gcodes)
        self.logger.info("...done loading gcodes")

    def run(self):
        self.logger.info("Start print from SD Card")
        self.sender.stop_temp_requesting_flag = True
        self.sender.temperature_request_thread.join()
        if not self.send(self.START_WRITE_FILE_GCODE):
            return
        self.logger.info("Starting upload gcodes...")
        for line in self.buffer:
            if self.sender.stop_flag:
                return
            self.sender.connection.send(line)
        for gcode in [self.END_WRITE_FILE_GCODE, self.START_PRINT_FROM_FILE]:
            if not self.send(gcode):
                return
        self.logger.info("done")
        next_send_request = 0
        self.logger.info("Starting monitoring...")
        while self.print_thread_flag:
            time_now = time.monotonic()
            if time_now >= next_send_request:
                next_send_request = time_now + 15
                if not self.send("M27"):
                    return
                if not self.send("M105"):
                    return
            if self.sender.stop_flag:
                return
            time.sleep(0.1)
        self.logger.info("done")
        self.sender.start_temp_requesting()

    def get_percent(self):
        if self.lines_sent <= 0 or not self.total_gcodes:
            return 0
        else:
            return int(self.lines_sent / float(self.total_gcodes) * 100)

    def send(self, line, add_checksum=False):
        super(SDCardPrintThread, self).send(line, add_checksum)
        while not self.sender.ready_for_command.wait(1):
            if self.sender.stop_flag:
                return
        return True

class Sender(threaded_sender.Sender):

    DOUBLE_OK_HANDLING = False

    def load_gcodes(self, gcodes):
        self.logger.info("Loading gcodes in ThreadedSender")
        with self.thread_start_lock:
            if (self.print_thread and self.print_thread.is_alive()):
                self.logger.info("Joining printing thread...")
                self.print_thread.join(timeout=self.PRINT_JOIN_TIMEOUT)
                if self.print_thread.is_alive():
                    message = "Can't start print cause already printing"
                    self.parent.register_error(260, message, is_blocking=False)
                    return False
            self.print_thread = SDCardPrintThread(self)
            self.print_thread.load_gcodes(gcodes)
            self.print_thread.start()
            self.logger.info("Print thread started")

    def define_regexps(self):
        threaded_sender.Sender.define_regexps(self)
        self.progress_re = re.compile(".*SD printing byte (\d+)\/(\d+).*", flags=re.DOTALL)

    def pause(self):
        # if self.print_thread:
        #     if not self.print_thread.paused:
        #         self.print_thread.send("M25", add_checksum=False)
        #         self.print_thread.paused = True
        #         return True
        message = "This feature do not supported"
        self.parent.register_error(902, message, is_blocking=False)
        return False

    def unpause(self):
        # if self.print_thread:
        #     if self.print_thread.paused:
        #         self.print_thread.send("M24", add_checksum=False)
        #         self.print_thread.paused = False
        #         return True
        message = "This feature do not supported"
        self.parent.register_error(901, message, is_blocking=False)
        return False

    def log_strange_acks(self, line):
        if self.print_thread:
            if "Done printing file" in line:
                self.print_thread.print_thread_flag = False
                self.print_thread.lines_sent = self.print_thread.total_gcodes
            elif "SD printing" in line:
                progress_match = self.progress_re.match(line)
                if progress_match:
                    self.print_thread.lines_sent = int(progress_match.group(1))
                    self.print_thread.total_gcodes = int(progress_match.group(2))
                    if self.print_thread.total_gcodes > 0:
                        if self.print_thread.lines_sent >= self.print_thread.total_gcodes:
                            self.print_thread.print_thread_flag = False
        elif "error" in line or "failed" in line:
            self.operational_flag = False
            self.parent.register_error(900, line, is_blocking=True)
        else:
            self.logger.warning("Received strange answer: " + line.strip())

    def is_ok(self, line):
        for ack in self.positive_acks:
            if line.startswith(ack):
                return True
        if 'closefile' in line:
            return True
        return self.ok_re.match(line)


if __name__ == "__main__":
    import log

    log.create_logger("", "debug_log.txt")
    usb_info = {'COM': "COM18"}
    profile = {
        'baudrate': [115200],
        "name": "Test",
        "alias": "Test",
        "no_DTR": False,
        "end_gcodes": [
            "M104 S0",
            "M140 S0",
            "G28 X0 Y0",
            "M84"
        ]
    }
    with open("test_gcodes.gco", "r") as f:
        your_file_content = f.read()
    sender = Sender(profile, usb_info)
    sender.connection.verbose = True
    time.sleep(4)
    print("start print")
    sender.load_gcodes(your_file_content)
    time.sleep(3)
    try:
        while True:
            write = input(">: ").strip()
            sender.connection.send(write)
    except:
        pass
