#!/usr/bin/env python
# coding=utf-8
#-----------------------------------------------------------------------
# Copyright © 2014-2023 Tormach® Inc. All rights reserved.
# License: GPL Version 2
#-----------------------------------------------------------------------

#pylint: disable=import-error


#--------------------------------------------------------------------------
# define these BEFORE importing them from interpreter
# so that pylint doesn't false positive on undefined variable
# they will get overwritten by the interpreter from import syntax
INTERP_ERROR = 0
INTERP_OK = 0
INTERP_EXECUTE_FINISH = 0
TOLERANCE_EQUAL = 0
INVERSE_TIME = 0
#--------------------------------------------------------------------------


from interpreter import *
import emccanon

from stdglue import cycle_prolog, cycle_epilog, init_stdglue
from remap_common import *

import inspect
import re
import math
import rs274
from remap_classes import _g7x
from pdb import set_trace as bp

import redis


def g71(self, **words):
    obj = _g7x(self, 710, **words)
    return obj.run()

def g711(self, **words):
    obj = _g7x(self, 711, **words)
    return obj.run()

def g72(self, **words):
    obj = _g7x(self, 720, **words)
    return obj.run()

def g721(self, **words):
    obj = _g7x(self, 721, **words)
    return obj.run()

def g740(self, **words):
    # G74 is typically called like so:
    # G74 x ? z ? k ? (x = diameter, usually 0; z = hole endpoint; k = peck length)

    # get position
    if (self.params['_lathe_diameter_mode']):  # if is_lathe_mode
        x_mode = 2
    else:
        x_mode = 1

    x_start = self.params[5420] * x_mode
    z_start = self.params[5422]

    if 'x' in words:
        x_end = words['x'] * x_mode
    else:
        x_end = x_start * x_mode

    if 'z' in words:
        z_end = words['z']
        if z_end > z_start:
            return "G74 error - Z cannot be larger than the starting Z position"
    else:
        z_end = z_start

    if 'k' in words:
        peck_length = words['k']
        if peck_length < 0:
            return "G74 error - K cannot be negative"
    else:
        peck_length = 0

    if (self.params['_metric']):  # if is_metric
        backoff_length = 0.50  # mm
        rounding_fudge = 0.0001
    else:
        backoff_length = 0.020  # inch
        rounding_fudge = 0.00001

    z_range = math.fabs(z_end - z_start) - rounding_fudge  # rounding_fudge prevents extra peck
    if peck_length > 0:
        num_pecks = int(z_range / peck_length)
    else:
        num_pecks = 0

    z_list = []
    for i in range(num_pecks + 1):
        z_list.append(z_start - (i * peck_length))
    z_list.append(z_end)

    print "--kaw - z_list =", z_list

    # Explicitly format float values to avoid scientific notation
    if math.fabs(x_end - x_start) > rounding_fudge:  # We're groove'n
        for i in range(num_pecks + 1):
            self.execute("G0 Z {:0.8f}".format(z_list[i]))
            self.execute("G1 Z {:0.8f}".format(z_list[i + 1]))
            self.execute("G1 X {:0.8f}".format(x_end))
            self.execute("G1 Z {:0.8f}".format(z_list[i] + backoff_length))
            self.execute("G0 X {:0.8f}".format(x_start))

    else:  # We're drilling
        for i in range(num_pecks + 1):
            self.execute("G1 Z {:0.8f}".format(z_list[i + 1]))
            self.execute("G0 Z {:0.8f}".format((z_list[i + 1] + backoff_length)))

    self.execute("G0 Z {:0.8f}".format(z_start))

    return INTERP_OK


def g300(self, **words):
    return g300_common(self, 'xz', **words)

# My custom M Codes
#--------------------------------------------------------------------------
import sys
from interpreter import *
from Tkinter import *
import linuxcnc

list = []
#lc = linuxcnc.command()

# Add an entry
def m21remap(self, **words):
	#global list
	if not self.task: return INTERP_OK
	#list.append(self.blocks[self.remap_level].comment)
	self.error_handler.write("M21")
	return INTERP_OK

def done(self, dialog, entries):
	for entry in entries:
		val = entry[1].get()
		if val.isdigit:
			self.params[entry[0]] = float(val)
	dialog.update()
	dialog.destroy()

# Show the entries
def m22remap(self, **words):
	if not self.task: return INTERP_OK
	#dir(self)
	#global list
	#entries = []
	#row = 1
	#if not hasattr(sys, 'argv'):
		#sys.argv  = ['']	
	#dialog = Tk()
	#dialog.title(self.blocks[self.remap_level].comment)
	#for item in list:
	#	ret = item.split(";")
	#	prompt = ret[0]
	#	if len(ret) == 1:
	#		param = "_" + prompt.replace(" ", "")
	#	else:
	#		param = "_" + ret[1].replace(" ", "")
	#	Label(dialog, text=prompt).grid(row=row)
	#	entry = Entry(dialog)
	#	entry.grid(column=1, row=row)
	#	try:
	#		entry.insert(0, self.params[param])
	#	except:
	#		pass
	#	entries.append((param, entry))
	#	row += 1
	#b1 = Button(dialog, text='OK', command=lambda: done(self, dialog, entries))
	#b1.grid(row=row, column=0, sticky=W, pady=4)
	#b1.focus()
	#b1.bind('<Return>', lambda x: done(self, dialog, entries))
	#mainloop()
	self.error_handler.write("M22")
	return INTERP_OK

def m20remap(self, **words):
	#global list
	#list = []
	self.error_handler.write("M20")
	return INTERP_OK

def m23remap(self, **words):
	sys.argv = ['']
	#dialog = Tk()
	self.error_handler.write("M23")
	return INTERP_OK
#---------------------------------------------------------------------------------

def spindle_prolog(self, **words):
    # read the redis pref on clamping style and setup a parameter that the ngc code
    # can simply compare P21 against to see if it should block M3 or M4.
    # have to do it this way so the ngc code can easily force a queue buster
    # to align interp time with real time so that we are checking the collet status
    # at the right time!!

    clamping_style = self.redis.hget('machine_prefs', 'auto_collet_closer_clamping_style')
    if clamping_style == 'OD':
        self.params['_collet_status_block_spindle_value'] = 1

    elif clamping_style == 'ID':
        self.params['_collet_status_block_spindle_value'] = 0

    else:
        self.set_errormsg("Unsupported clamping style {}.".format(clamping_style))
        return INTERP_ERROR

    return INTERP_OK


def digital_io_output_on_immediate_M64(self,**words):
    pin = int(words.get('p', -1))
    if pin != 2:
        if int(words.get('q', 0)) == 1:
            emccanon.TOGGLE_AUX_OUTPUT_BIT(pin)
        else:
            emccanon.SET_AUX_OUTPUT_BIT(pin)
        return INTERP_OK
    else:
        self.set_errormsg("M64 P2 is no longer supported for collet clamping control.  Replace with either M10 for 'unclamp' or M11 for 'clamp' as these properly use the OD/ID clamp setting and perform a spindle safety check.")
        return INTERP_ERROR


def digital_io_output_off_immediate_M65(self,**words):
    pin = int(words.get('p', -1))
    if pin != 2:
        emccanon.CLEAR_AUX_OUTPUT_BIT(pin)
        return INTERP_OK
    else:
        self.set_errormsg("M65 P2 is no longer supported for collet clamping control.  Replace with either M10 for 'unclamp' or M11 for 'clamp' as these properly use the OD/ID clamp setting and perform a spindle safety check.")
        return INTERP_ERROR


def save_user_modals_M80 (self, **words):
    #----------------------------------------------------------------------------------
    #  various Tormach procedures change the motion mode, feed rate, and override status
    #   this custom save M code save important user context globally.  M70, and M72, normally
    #   used for this function, do not preserve values globally, and therefore cannot be used
    #   restore state before a low level subroutine aborts execution
    #-------------------------------------------------------------------------------------
    if self.task == 0:
        return INTERP_OK

    if not self.M80_state:
        self.params["_global_saved_user_G91"]        = self.params["_incremental"]   #1 for G1, 0 for G90
        self.params["_global_saved_user_feed"]       = self.params["_feed"]          #F value
        self.params["_global_saved_feed_enable"]     = self.params["_feed_override"] #enabled or disabled
        self.params["_global_saved_metric"]          = self.params["_metric"]        #imperial or metric
        self.params["_global_saved_speed_enable"]    = self.params["_speed_override"] #speed override
        self.params["_global_saved_upr_mode"]        = self.params["_units_per_rev"]  #units per rev mode
        self.params["_global_saved_it_mode"]         = self.params["_inverse_time"]   #inverse time mode

        self.error_handler.log("M80 save : I({:.0f}) F({:.0f}) FO({:.0f}) M({:.0f}) SO({:.0f}) UPR({:.0f})  IT({:.0f})) ".format(self.params["_incremental"], \
                                                                       self.params["_feed"], \
                                                                       self.params["_feed_override"], \
                                                                       self.params["_metric"],\
                                                                       self.params["_speed_override"],\
                                                                       self.params["_units_per_rev"],\
                                                                       self.params["_inverse_time"]))
        self.M80_state = True
    else:
        self.error_handler.log("M80 context save, but already have saved context that hasn't been restored yet with M81 that would be lost so ignoring...")

    return INTERP_OK


def restore_user_modals_M81 (self, **words):
    #----------------------------------------------------------------------------------
    #  ham and eggs -
    #   restore user state saved in a prior M80  - note this is for INTERNAL USE by Tormach.
    #   Failure to set valid values in M80 results in defaults settings . See top level
    #   Not to be release to the public
    #-------------------------------------------------------------------------------------

    if self.task == 0:
        return INTERP_OK

    # see g370_prolog for what this is for
    self.redis.hdel('remap_ipc', 'm00_m01_comment')

    if self.M80_state:
        #restore users prior motion type
        if self.params["_global_saved_user_G91"] == 1.0 :
            self.execute("G91")
        else:
            self.execute("G90")

        #restore users feed override enablement
        if self.params["_global_saved_feed_enable"] == 1.0 :
            self.execute("M50 P1")
        else:
            self.execute("M50 P0")

        #restore users speed override enablement

        if self.params["_global_saved_speed_enable"] == 1.0 :
            self.execute("M51 P1")
        else:
            self.execute("M51 P0")

        #restore users metric/imperial thing
        if  self.params["_global_saved_metric"] == 1.0:
            self.execute("G21")
        else:
            self.execute("G20")

        #restore feed rate mode (unit per rev, inverse time, or feed per min)
        # only one will be active per customer
        if self.params["_global_saved_upr_mode"] == 1.0: # feed per rev
            self.execute("G95")
        elif self.params["_global_saved_it_mode"] == 1.0: #inverse time
            self.execute("G93")
        else:                                         #only other possiblity for this one
            self.execute("G94")

        # restore users prior feed rate
        # MUST do this AFTER G93/G94/G95 as they often reset the F word back to 0 and then we haven't
        # effectively restored state properly.
        self.execute("F "+ str(self.params["_global_saved_user_feed"]) )

        self.M80_state = False
        self.error_handler.log("M81 restore : I({:.0f}) F({:.0f}) FO({:.0f}) M({:.0f}) SO({:.0f}) UPR({:.0f}) IT({:.0f})".format(self.params["_incremental"], \
                                                                           self.params["_feed"], \
                                                                           self.params["_feed_override"], \
                                                                           self.params["_metric"],\
                                                                           self.params["_speed_override"],\
                                                                           self.params["_units_per_rev"],\
                                                                           self.params["_inverse_time"] ))
    else:
        self.error_handler.log("User context restore requested without prior M80, ignoring......")

    return INTERP_OK


def start_video_recording_M301(self, **words):
    if self.task == 0:
        # preview
        yield INTERP_OK    
    else:        
        # Queue buster!  Go finish all q entries and then call us back
        yield INTERP_EXECUTE_FINISH

        # I once saw in the log the redis.save() method toss an exception
        # of redis.exceptions.ResponseError: Background save already in progress
        # RedisError is the base class of all/most of everything redis can raise
        # so use that.
        try:
            self.start_video_recording_count += 1
            self.redis.hset('machine_prefs', 'start_video_recording', str(self.start_video_recording_count))
            self.redis.save()   #force all data out
        except redis.exceptions.RedisError as e:
            msg = "{0} occured, these were the arguments:\n{1!r}".format(type(e).__name__, e.args)
            self.error_handler.log("M301: caught a RedisError during save attempt: {:s}".format(msg))

        self.error_handler.log("M301: Start video recording")
        yield INTERP_OK


def stop_video_recording_M302(self, **words):
    if self.task == 0:
        # preview
        yield INTERP_OK
    else:
        # Queue buster!  Go finish all q entries and then call us back
        yield INTERP_EXECUTE_FINISH

        # I once saw in the log the redis.save() method toss an exception
        # of redis.exceptions.ResponseError: Background save already in progress
        # RedisError is the base class of all/most of everything redis can raise
        # so use that.
        try:
            self.stop_video_recording_count += 1        
            self.redis.hset('machine_prefs', 'stop_video_recording', str(self.stop_video_recording_count))
            self.redis.save()   #force all data out
        except redis.exceptions.RedisError as e:
            msg = "{0} occured, these were the arguments:\n{1!r}".format(type(e).__name__, e.args)
            self.error_handler.log("M302: caught a RedisError during save attempt: {:s}".format(msg))

        self.error_handler.log("M302: Stop video recording")
        yield INTERP_OK


def take_picture_M303(self, **words):
    if self.task == 0:
        # preview
        yield INTERP_OK
    else:
        # Queue buster!  Go finish all q entries and then call us back
        yield INTERP_EXECUTE_FINISH

        # I once saw in the log the redis.save() method toss an exception
        # of redis.exceptions.ResponseError: Background save already in progress
        # RedisError is the base class of all/most of everything redis can raise
        # so use that.
        try:
            self.picture_count += 1
            self.redis.hset('machine_prefs', 'take_picture', str(self.picture_count))
            self.redis.save()   #force all data out
        except redis.exceptions.RedisError as e:
            msg = "{0} occured, these were the arguments:\n{1!r}".format(type(e).__name__, e.args)
            self.error_handler.log("M303: caught a RedisError during save attempt: {:s}".format(msg))

        self.error_handler.log("M303: Take picture")
        yield INTERP_OK
