# 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 os
import time
import zipfile

import threaded_sender

from base_sender import BaseSender
from gcodes_buffer import GcodesBuffer


class Sender(threaded_sender.Sender):

    # 8.3 is mandatory for some file some firmwares, but not RepRapFirmware
    SD_CARD_FILENAME = b"printros.gco"
    LIST_SD_CARD_GCODE = b"M20"
    INITIALIZE_SD_CARD_GCODE = b"M21"
    RELEASE_SD_CARD_GCODE = b"M22"
    SELECT_SD_CARD_FILE_GCODE = b"M23"
    START_OR_RESUME_SD_CARD_PRINT_GCODE = b"M24"
    PAUSE_SD_CARD_PRINT_GCODE = b"M25"
    SET_SD_CARD_PRINT_POSITION_GCODE = b"M26"
    REPORT_SD_CARD_PRINT_STATUS_GCODE = b"M27"
    BEGIN_WRITING_SD_CARD_GCODE = b"M28"
    STOP_WRITING_SD_CARD_GCODE = b"M29"
    DELETE_SD_CARD_FILE_GCODE = b"M30"
    SELECT_FILE_AND_START_SD_CARD_PRINT_GCODE = b"M32"

    # REPRAPFIRMWARE specific gcodes
    REPORT_SD_CARD_INFO_GCODE = b"M39"

    PAUSE_ON_REPRAPFIRMWARE_GCODE = b"M226"

    SIMULATION_MODE_GCODE = b"M37"
    SIMULATION_MODE_ON_GCODE = SIMULATION_MODE_GCODE + b" S1"
    SIMULATION_MODE_OFF_GCODE = SIMULATION_MODE_GCODE + b" S0"
    ALLOW_COLD_EXTR_GCODE = b"M302"

    RETURN_FILE_INFORMATION_GCODE = b"M36"
    REQUEST_JSON_REPORT_GCODE = b"M408"
    # END OF REPRAPFIRMWARE specific gcodes


    TEMP_REQUEST_THREAD_GCODES = (REPORT_SD_CARD_PRINT_STATUS_GCODE, threaded_sender.Sender.GET_TEMP_GCODE)
    """
    M27 resp  SD printing byte 2134/235422


    Marlin 1.1.9 and up M27 Sn sets the auto-report interval. This requires the AUTO_REPORT_SD_STATUS configuration option to be enabled. Marlin reports this capability in M115 as Cap: AUTO_REPORT_SD_STATUS 1 when this option is available.

    Example

    M27 S2 ; Report the SD card status every 2 seconds
    """

    def __init__(self, parent, usb_info, profile):
        super().__init__(parent, usb_info, profile)
        self.current_byte_pos = 0
        self.total_bytes = 0

    def gcodes(self, filepath):
        if self.is_printing():
            self.parent.register_error(260, "Error: already printing - unable to start a new print")
            return False
        #TODO add echo about print start
        self.logger.info("Start loading gcodes...")
        self.stop_temp_requesting()
        size = 0
        #self.delete_sd_file()
        time.sleep(0.1) #TODO this is a hack, get idea why it is needed
        self._send_now(threaded_sender.WriteThread.GO_TO_LINE_N_GCODE + b" N0")
        self._send_now(self.BEGIN_WRITING_SD_CARD_GCODE + b" " + self.SD_CARD_FILENAME)
        self.gcode_filepath = filepath
        self.logger.info('Creating gcodes buffer')
        buffer = GcodesBuffer(filepath, self, self.MAX_VALID_RESEND_RANGE, blocking_count=True)
        self.logger.info('Sending the buffer to Sender.load_gcodes')
        threaded_sender.Sender.load_gcodes(self, buffer, False, False)
        self.logger.info('Waiting to gcode file lines count')
        while not self.write_thread.total_gcodes:
            if self.stop_flag:
                return False
            time.sleep(1)
        total_gcodes = self.write_thread.total_gcodes
        self.logger.info(f'Counted {total_gcodes} line to print. Starting upload lines to sdcard')
        while self.write_thread and self.write_thread.lines_sent != total_gcodes and self.write_thread.is_alive():
            self.logger.info(f'Uploaded {self.write_thread.lines_sent} lines')
            if self.stop_flag:
                return False
            time.sleep(1)
        self._send_now(self.STOP_WRITING_SD_CARD_GCODE)
        self.start_temp_requesting()
        self.start_printing_sdcard_file()
        if not self.keep_print_files:
            try:
                os.remove(filepath)
            except:
                pass
        self.filesize = size
        self.current_line_number = 0
        self.printing_flag = True
        return True

    def start_printing_sdcard_file(self, filename=None):
        if not filename:
            filename = self.SD_CARD_FILENAME
        self._send_now(self.SELECT_SD_CARD_FILE_GCODE + b" " + filename)
        self._send_now(self.START_OR_RESUME_SD_CARD_PRINT_GCODE)

    def is_printing(self):
        return self.printing_flag

    def is_paused(self):
        return self.pause_flag

    def pause(self, _=False):
        if not self.is_printing():
            self.parent.register_error(254, "No print to pause")
        elif self.is_paused():
            self.parent.register_error(254, "Already in pause")
        else:
            self._send_now(self.PAUSE_SD_CARD_PRINT_GCODE)
            self.pause_flag = True
            return True
        return False

    def resume(self, _=False):
        if self.is_paused():
            self._send_now(self.START_OR_RESUME_SD_CARD_PRINT_GCODE)
            self.pause_flag = False
            return True
            self.parent.register_error(254, "Can't resume - not paused")
        return False

    def cancel(self):
        if self.is_printing():
            try:
                if self.write_thread:
                    self.write_thread.send_now_buffer.clear()
                    if self.write_thread.buffer:
                        self.write_thread.buffer.clear()
            except:
                pass
            self._send_now(self.PAUSE_SD_CARD_PRINT_GCODE)
            self._send_now(b"M0")
            self.pause_flag = False
            time.sleep(1)
            self.sync_ready_for_command()
            return True
        return False

    def delete_sd_file(self, filename=None):
        if not filename:
            filename = self.SD_CARD_FILENAME
        self._send_now(self.DELETE_SD_CARD_FILE_GCODE + b" " + filename)

    def _parse_sdcard_printing_progress(self, line=b""):
        words = line.split()
        if words:
            current_bytes, total_bytes = words[-1].split(b'/')
            current_bytes_int, total_bytes = int(current_bytes), int(total_bytes)
            self.current_byte_pos = current_bytes_int
            self.total_bytes = total_bytes
            self.printing_flag = True
        else:
            self.current_byte_pos = 0
            self.total_bytes = 0
            self.printing_flag = False

    def get_percent(self):
        if self.total_bytes and self.current_byte_pos:
            return round(self.current_byte_pos / self.total_bytes * 100, 2)
        return 0.0


if __name__ == "__main__":
    import logging
    import sys

    import _fake_parent
    import user_login
    import config

    settings = config.get_settings()
    settings['keep_print_files'] = True
    print("Keep:", config.get_settings()['keep_print_files'])


    PRINTER_PROFILE_NAME = 'LOOP3DPROPLUS'
    USB_INFO = {'VID': '2341', 'PID': '0010', 'SNR': '0001', 'COM': '/dev/null', 'EMU': True}
    FILENAME = '_test.gcode'

    root = logging.getLogger('')
    handler = logging.StreamHandler(sys.stdout)
    handler.setLevel(logging.DEBUG)
    root.addHandler(handler)

    class NoPrinterProfileExcepton(Exception):

        def __init__(self, profile_name):
            super().__init__(f'No profile with alias: {profile_name}')

    def load_profile(profile_alias):
        profiles = user_login.UserLogin.load_local_printer_profiles()
        for profile in profiles:
            if profile['alias'] == profile_alias:
                return profile
        raise NoPrinterProfileExcepton

    fp = _fake_parent.FakeParent()
    sender = Sender(fp, usb_info={"COM": "/dev/ttyACM0"}, profile=load_profile(PRINTER_PROFILE_NAME))
    sender.connection.verbose = True
    sender._send_now(sender.SIMULATION_MODE_ON_GCODE)
    sender.gcodes(os.path.abspath(FILENAME))
    while True:
        print("Percent:", sender.get_percent())
        print("Temps:", sender.get_temps())
        try:
            time.sleep(1)
        except KeyboardInterrupt:
            break
    sender._send_now(sender.SIMULATION_MODE_OFF_GCODE)
    sender.close()
    time.sleep(1)
