#!/usr/bin/env python3

#GladeVCP widget based on drowidget and HAL_LED

import os, re, math
import linuxcnc
import traceback as tb
import gi
gi.require_version('Gtk','3.0')
gi.require_version('Gdk','3.0')
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import GObject
from gi.repository import GLib
from gladevcp import led as Gled
from gladevcp import makepins

try:
    INIPATH = os.environ['INI_FILE_NAME']
except:
    pass

class LED_DRO(Gtk.Bin):

    __gtype_name__ = 'LED_DRO'
    __gproperties__ = {
        'display_units_mm': ( GObject.TYPE_BOOLEAN, 'Display Units', 'Display in metric or not',
                    False, GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
        'actual' : ( GObject.TYPE_BOOLEAN, 'Actual Position', 'Display Actual or Commanded Position',
                    True, GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
        'diameter' : ( GObject.TYPE_BOOLEAN, 'Diameter Adjustment', 'Display Position As Diameter',
                    False, GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
        'mm_text_template': ( GObject.TYPE_STRING, 'Text template for metric', 'Text template to display',
                    "%10.2f", GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
        'imperial_text_template': ( GObject.TYPE_STRING, 'Text template for imperial', 'Text template to display',
                    "%9.2f", GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
        'joint_number': ( GObject.TYPE_INT, 'Joint Number', 'X=0, Y=1, Z=2, etc.',
                    0, 8, 0, GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
        'absolute': ( GObject.TYPE_BOOLEAN, 'Reference Type', 'True = Absolute, False = Relative',
                    True, GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
        'font_family' : ( GObject.TYPE_STRING, 'Font Family', 'The font family of the DRO text: e.g. mono',
                    "sans", GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
        'font_size' : ( GObject.TYPE_INT, 'Font Size', 'The size of the DRO text from 8 to 96',
                    8, 96, 26, GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
        'font_weight' : ( GObject.TYPE_STRING, 'Font Weight', 'The size of the DRO text: lighter, normal, bold, or bolder',
                    "bold", GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
        'unhomed_color' : ( GObject.TYPE_STRING, 'Unhomed Color', 'The color of the DRO LED when not homed',
                    'dark', GObject.ParamFlags.READWRITE),
        'homed_color' : ( GObject.TYPE_STRING, 'Homed Color', 'The color of the DRO LED when homed',
                    'green', GObject.ParamFlags.READWRITE),
        }
    __gproperties = __gproperties__
    AXES = ("X: ","Y: ","Z: ","A: ","B: ","C: ","U: ","V: ","W: ")

    def __init__(self, *a, **kw):

        self.css = Gtk.CssProvider()     
        #gproperties become attributes of self after running super().__init__, but css_text must be defined before calling it
        self.css_text = f""".led_dro {{     
            font-family: {self.__gproperties['font_family'][3]};
            font-size: {self.__gproperties['font_size'][3]}px;
            font-weight: {self.__gproperties['font_weight'][3]};
            background-color: #000000;
            color: #FF0000
        }}
        """

        super().__init__(*a, **kw)  #must be called after css_text is defined
        
        self.css.load_from_data(bytes(self.css_text, 'utf-8'))
        self.get_style_context().add_provider(self.css,Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
        self.get_style_context().add_class('led_dro')

        try:
            self.inifile = linuxcnc.ini(INIPATH)
            units=self.inifile.find("TRAJ","LINEAR_UNITS")
            self.linuxcnc = True
            self.stat = linuxcnc.stat()
            if units==None:
                units='mm'
        except:
            units='inch'
            self.linuxcnc = False
        if units in ("mm","metric","1.0"):
            self.machine_units_mm = 1
            conv_mask = [1.0/25.4]*3+[1]*3+[1.0/25.4]*3
        else:
            self.machine_units_mm = 0
            conv_mask = [25.4]*3+[1]*3+[25.4]*3
        self.set_machine_units(self.machine_units_mm, conv_mask)

        try:
            self.box = Gtk.HBox(spacing = 6)
            self.label = Gtk.Label()
            self.box.pack_start(self.label, True, True, 0)

            self.led = Gled.HAL_LED()
            self.led.set_color('on', self.__gproperties['homed_color'][3])
            self.led.set_color('off', self.__gproperties['unhomed_color'][3])
            self.box.pack_start(self.led, True, True, 0)
            
            self.add(self.box)
        except:
            tb.print_exc()

        GLib.timeout_add(100, self.periodic)
    
    def set_machine_units(self, u, c):
        self.machine_units_mm = u
        self.unit_convert = c
    
    def convert_units(self, v):
        c = self.unit_convert
        return list(map(lambda x,y: x*y, v, c))

    def set_to_inch(self):
        self.display_units_mm = 0

    def set_to_mm(self):
        self.display_units_mm = 1

    def set_to_diameter(self):
        self.diameter = True

    def set_to_radius(self):
        self.diameter = False

    def do_get_property(self, property):
        name = property.name.replace('-', '_')
        if name in list(self.__gproperties.keys()):
            return getattr(self, name)
        else:
            raise AttributeError('unknown property %s' % property.name)
        
    def do_set_property(self, property, value):
        name = property.name.replace('-', '_')
        match name:

            case 'mm_text_template'|'imperial_text_template':
                try:
                    v = value % 0.0
                except:
                    print(f"Invalid format string '{value}' for '{name}'")

            case 'font_family':
                if not isinstance(value, str):
                    print(f"Invalid font family '{value}'")
                    value = 'sans'
                self.set_style('family', value)

            case 'font_size':
                try:
                    v = int(value)
                    if v not in range(8,97):
                        print(f"Font size '{value}' invalid, must be int between 8 and 96")
                        value = 26
                except:
                    print(f"Font size '{value}' invalid, must be int between 8 and 96")
                    value = 26
                self.set_style('size', value)
            
            case 'font_weight':
                if value not in ('lighter', 'normal', 'bold', 'bolder'):
                    print("Invalid font weight '{value}'" \
                          "must be 'lighter', 'normal', 'bold', or 'bolder'")
                    value = 'bold'
                self.set_style('weight', value)
            
        if name in list(self.__gproperties.keys()):
            setattr(self, name, value)
            self.queue_draw()
        else:
            raise AttributeError('unknown property %s' % property.name)
    
    def set_style(self, property, value):
        match property:
            case 'family':
                old = '-fam.*'
                new = f"-family: {value};"
            case 'size':
                old = '-siz.*'
                new = f"-size: {value}px;"
            case 'weight':
                old = '-wei.*'
                new = f"-weight: {value};"
            case 'background':
                old = 'background-col.*'
                new = f"background-color: {value};"
            case 'labelcolor':
                old = 'color.*'
                new = f"color: {value};"
            case _:
                print(f"'{property}' is an unknown property")
                return
        self.css_text = re.sub(old, new, self.css_text, re.IGNORECASE)
        try:
            self.css.load_from_data(bytes(self.css_text, 'utf-8'))
        except:
            print(f"'{value}' is an invalid value for '{property}'")
        self.queue_draw()

    def periodic(self):
        if self.linuxcnc:
            self.stat.poll()
            absolute, relative = self.position()
        else:
            absolute = relative = [9999.999,0,0,0,0,0,0,0,0]
        if self.display_units_mm:
            tmpl = lambda s: self.mm_text_template % s
        else:
            tmpl = lambda s: self.imperial_text_template % s
        scale = 2.0 if self.diameter else 1
        if self.absolute:
            self.label.set_text(self.AXES[self.joint_number] + tmpl(absolute[self.joint_number]*scale))
        else:
            self.label.set_text(self.AXES[self.joint_number] + tmpl(relative[self.joint_number]*scale))
        
        return True
    
    def position(self):
        if self.actual:
            p = self.stat.actual_position
        else:
            p = self.stat.position
        
        relp = [0,0,0,0,0,0,0,0,0]
        for axis in range(9):
            relp[axis] = p[axis] - self.stat.g5x_offset[axis] - self.stat.tool_offset[axis] 
        
        if self.stat.rotation_xy != 0:
            t = math.radians(-self.stat.rotation_xy)
            xr = relp[0] * math.cos(t) - relp[1] * math.sin(t)
            yr = relp[0] * math.sin(t) + relp[1] * math.cos(t)
            relp[0] = xr
            relp[1] = yr
        
        for axis in range(9):
            relp[axis] -= self.stat.g92_offset[axis]

        if self.display_units_mm != self.machine_units_mm:
            p = self.convert_units(p)
            relp = self.convert_units(relp)

        return p, relp

def main():
    window = Gtk.Dialog("My dialog", None, modal = True,
                        destroy_with_parent = True)
    window.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT,
                        Gtk.STOCK_OK, Gtk.ResponseType.ACCEPT)
    dro = LED_DRO()
    window.vbox.add(dro)
    window.connect('destroy', Gtk.main_quit)
    window.show_all()
    response = window.run()
    if response == Gtk.ResponseType.ACCEPT:
        print('ok')
    else:
        print('cancel')

if __name__ == '__main__':
    main()