import ctypes
from enum import IntEnum

# command IDs
class CommandType(IntEnum):
    iddInitDrivers = 11;
    iddStartFile   = 12;
    iddEndFile     = 13;
    iddPcodeTerminator  = 14;
    iddJobParms    = 102;
    iddStartJob    = 103;
    iddEndJob      = 104;
    iddToolChange  = 108;
    iddStartPass   = 109;
    iddStartPassList    =  23;
    iddEndPass     = 110;
    iddStartContour     = 105;
    iddEndContour  = 106;
    iddDrawXYZ     = 101;
    iddDrawArc     = 107;
    iddDrawCircle  = 113;
    iddStartArc    = 111;
    iddEndArc      = 112;
    iddSetFeed     = 114;
    iddSetPlunge   = 115;
    iddSetSpin     = 116;
    iddSetMachinePlane  = 117;
    iddCycleStart  = 118;
    iddCyclePoint  = 119;
    iddCycleEnd    = 120;
    iddComment     = 121;
    iddMessage     = 122;
    iddDwell       = 123;
    iddSetForce    = 124;
    iddSetAccel    = 125;
    iddPause       = 126;
    iddToolComp    = 127;
    iddChangePanel = 128;
    iddTextType    = 129;
    iddPartData    = 130;
    iddNotchType   = 131;
    iddDrillType   = 132;
    iddPreframeAdvance  = 133;
    iddFrameAdvance     = 134;
    iddFileInformation  = 135;
    iddSGSJobParms = 136;
    iddResetMachine     = 137;
    iddRollFeed    = 138;
    
#Arc direction
class TArcDirection(IntEnum):
	PtArcCW		= 1;
	PtArcCCW	= 2;

# structures for the command data
class SMessageData(ctypes.Structure):
     _fields_ = [("msg", ctypes.c_char * 64)] 
     
class SInitDriverData(ctypes.Structure):
    _pack_ = 1
    _fields_ = [("model", ctypes.c_char * 128),
                ("driverName", ctypes.c_char * 128),
                ("driverFullName", ctypes.c_char * 256),
                ("hasArc", ctypes.c_bool),
                ("relativeMode", ctypes.c_bool),
                ("contZAxis", ctypes.c_int),
                ("hasSingleLayer", ctypes.c_bool),
                ("hasMultiPassCmd", ctypes.c_bool),
                ("multiPassMax", ctypes.c_long),
                ("machineSizeX", ctypes.c_double),
                ("machineSizeY", ctypes.c_double),
                ("machineSizeZ", ctypes.c_double),
                ("originCorner", ctypes.c_int),
                ("xDirection", ctypes.c_int),
                ("yDirection", ctypes.c_int),
                ("controllerLanguage", ctypes.c_char * 128),
                ("originator", ctypes.c_int)]
 
class SJobParmsData(ctypes.Structure):
    _pack_ = 1
    _fields_ = [("startJobX", ctypes.c_double),
                ("startJobY", ctypes.c_double),
                ("startJobZ", ctypes.c_double),
                ("xMin", ctypes.c_double),
                ("xMax", ctypes.c_double),
                ("yMin", ctypes.c_double),
                ("yMax", ctypes.c_double),
                ("xJobMin", ctypes.c_double),
                ("xJobMax", ctypes.c_double),
                ("yJobMin", ctypes.c_double),
                ("yJobMax", ctypes.c_double),
                ("xAlignToMax", ctypes.c_bool),
                ("yAlignToMax", ctypes.c_bool),
                ("hasXRefPoint", ctypes.c_bool),
                ("hasYRefPoint", ctypes.c_bool),
                ("xRefPoint", ctypes.c_double),
                ("yRefPoint", ctypes.c_double),
                ("arcTolerance", ctypes.c_double),
                ("arcMinAngle", ctypes.c_double),
                ("arcMaxAngle", ctypes.c_double),
                ("machineXResolution", ctypes.c_double),
                ("machineYResolution", ctypes.c_double),
                ("machineZResolution", ctypes.c_double),
                ("jobSize", ctypes.c_double),
                ("plotMode", ctypes.c_int),
                ("lengthUnit", ctypes.c_int),
                ("timeUnit", ctypes.c_int),
                ("speedUnit", ctypes.c_int)]
             
class SStartJobData(ctypes.Structure):
    _pack_ = 1
    _fields_ = [("targetX", ctypes.c_double),
                ("targetY", ctypes.c_double),
                ("liftZ", ctypes.c_double),
                ("modalX", ctypes.c_bool),
                ("modalY", ctypes.c_bool),
                ("modalZ", ctypes.c_bool)]
                
class SEndJobData(ctypes.Structure):
    _pack_ = 1
    _fields_ = [("targetX", ctypes.c_double),
                ("targetY", ctypes.c_double),
                ("liftZ", ctypes.c_double),
                ("modalX", ctypes.c_bool),
                ("modalY", ctypes.c_bool),
                ("modalZ", ctypes.c_bool)]
                
class SStartContourData(ctypes.Structure):
    _pack_ = 1
    _fields_ = [("targetX", ctypes.c_double),
                ("targetY", ctypes.c_double),
                ("liftZ", ctypes.c_double),
                ("modalX", ctypes.c_bool),
                ("modalY", ctypes.c_bool),
                ("modalZ", ctypes.c_bool),
                ("depthZ", ctypes.c_double),
                ("feed", ctypes.c_double),
                ("modalFeed", ctypes.c_bool),
                ("plunge", ctypes.c_double)]
                
class SEndContourData(ctypes.Structure):
    _pack_ = 1
    _fields_ = [("targetX", ctypes.c_double),
                ("targetY", ctypes.c_double),
                ("liftZ", ctypes.c_double),
                ("modalX", ctypes.c_bool),
                ("modalY", ctypes.c_bool),
                ("modalZ", ctypes.c_bool),
                ("feed", ctypes.c_double),
                ("modalFeed", ctypes.c_bool),
                ("plunge", ctypes.c_double)]     

class SStartArcData(ctypes.Structure):
    _pack_ = 1
    _fields_ = [("dummy", ctypes.c_short)]
    
class SDrawArcData(ctypes.Structure):
    _pack_ = 1
    _fields_ = [("targetX", ctypes.c_double),
                ("targetY", ctypes.c_double),
                ("targetZ", ctypes.c_double),
                ("modalX", ctypes.c_bool),
                ("modalY", ctypes.c_bool),
                ("modalZ", ctypes.c_bool),
                ("direction", ctypes.c_int),
                ("radius", ctypes.c_double),
                ("centerX", ctypes.c_double),
                ("centerY", ctypes.c_double),
                ("relCenterX", ctypes.c_double),
                ("relCenterY", ctypes.c_double),
                ("feed", ctypes.c_double),
                ("modalFeed", ctypes.c_bool),
                ("startAngle", ctypes.c_double),
                ("endAngle", ctypes.c_double),
                ("arcSize", ctypes.c_double)] 
                
class SEndArcData(ctypes.Structure):
    _pack_ = 1
    _fields_ = [("dummy", ctypes.c_short)]
                
class SDrawXYZData(ctypes.Structure):
    _pack_ = 1
    _fields_ = [("targetX", ctypes.c_double),
                ("targetY", ctypes.c_double),
                ("targetZ", ctypes.c_double),
                ("modalX", ctypes.c_bool),
                ("modalY", ctypes.c_bool),
                ("modalZ", ctypes.c_bool),
                ("feed", ctypes.c_double),
                ("modalFeed", ctypes.c_bool)] 
                
class SToolChangeData(ctypes.Structure):
    _pack_ = 1
    _fields_ = [("targetX", ctypes.c_double),
                ("targetY", ctypes.c_double),
                ("targetZ", ctypes.c_double),
                ("modalX", ctypes.c_bool),
                ("modalY", ctypes.c_bool),
                ("modalZ", ctypes.c_bool),
                ("spin", ctypes.c_double),
                ("toolNum", ctypes.c_long),
                ("toolName", ctypes.c_char * 64)] 
                
class SSetPlungeData(ctypes.Structure):
    _pack_ = 1
    _fields_ = [("plunge", ctypes.c_double)]   

class SSetFeedData(ctypes.Structure):
    _pack_ = 1
    _fields_ = [("feed", ctypes.c_double)] 

class SSetSpinData(ctypes.Structure):
    _pack_ = 1
    _fields_ = [("spin", ctypes.c_double)] 

class SDwellData(ctypes.Structure):
    _pack_ = 1
    _fields_ = [("dwellTime", ctypes.c_double)]    

class SDrillTypeData(ctypes.Structure):
    _pack_ = 1
    _fields_ = [("targetX", ctypes.c_double),
                ("targetY", ctypes.c_double),
                ("targetZ", ctypes.c_double),
                ("radius", ctypes.c_double),
                ("surface", ctypes.c_double),
                ("peck", ctypes.c_double),
                ("pecklift", ctypes.c_double),
                ("feedz", ctypes.c_double),
                ("dwell", ctypes.c_double),
                ("spindle", ctypes.c_double),
                ("drawCirc", ctypes.c_bool),
                ("drawLine", ctypes.c_bool),
                ("code", ctypes.c_short),
                ("drilltype", ctypes.c_short),
                ("reserved1", ctypes.c_double),
                ("reserved2", ctypes.c_double)]
                
class SSetMachinePlaneData(ctypes.Structure):
    _pack_ = 1
    _fields_ = [("workPlane", ctypes.c_ushort)]  
        
class SRollFeedData(ctypes.Structure):
    _pack_ = 1
    _fields_ = [("panelLength", ctypes.c_double)]    
    
class SStateParmsData(ctypes.Structure):
    _pack_ = 1
    _fields_ = [
                    # current position
                    ("currentX", ctypes.c_double),
                    ("currentY", ctypes.c_double),
                    ("currentZ", ctypes.c_double),
                    ("currentFeed", ctypes.c_double),
                    # Gcode Block numbering
                    ("blockNumber", ctypes.c_long), # current block number
                    ("blockNumberDelta", ctypes.c_long), # increment
                    ("blockLetter", ctypes.c_char * 64), # Letter of block number (usualy 'N')
                    ("eol", ctypes.c_char * 64) # End Of Line string (usualy CR+LF)
                ]  
    
PID180 = 3.1415926535 / 180.0
        
# base driver class
class DriverBase:
    def __init__(self):
        self.m_jp= None
        self.m_cfg = None
        self.m_sp = SStateParmsData(0, 1)
        self.m_model =""
        self.m_Zeros=0
        self.m_Diez=1
        self.m_FirstContour=1
        self.m_LastMode=-1
        self.m_LastFeed=-999.0
        self.m_LastTool=-1
        
    def NL(self):
        return '\r\n'

    def NB(self):
        self.m_sp.blockNumber += self.m_sp.blockNumberDelta
        return '{0}'.format(self.m_sp.blockNumber) + self.NL()
        
    def ltos(self, a):
        return f"{a}"
                
    def complexV2S(self, a):
        match(self.m_Diez):
            case 1:
                N = '{0:.1f}'.format(a)
                if (N == "-0.0"):
                    N = "0.0"
            case 2:
                N = '{0:.2f}'.format(a)
                if (N == "-0.00"):
                    N = "0.00"
            case 3:
                N = '{0:.3f}'.format(a)
                if (N == "-0.000"):
                    N = "0.000"
            case 4:
                N = '{0:.4f}'.format(a)
                if (N == "-0.0000"):
                    N = "0.0000"
            case 5:
                N = '{0:.5f}'.format(a)
                if (N == "-0.00000"):
                    N = "0.00000"
	
        if(self.m_Zeros == 0):
            while(N[len(N)-1:] =="0"):
                N = N[:len(N)-1]
        else:
            while(N[len(N)-1:] =="0" and N[len(N)-2:]!=".0"):
                N = N[:len(N)-1] 
        
        return N
        
    def setXYValue(self, a, modal, s ):
        if (not modal):
            return s + self.complexV2S(a)
        return ''
     
    def initDrivers(self,cfg):
        cfg_data = ctypes.cast(cfg, ctypes.POINTER(SInitDriverData))
        self.m_cfg = SInitDriverData.from_buffer_copy(cfg_data.contents)
        return ''
    def jobParams(self,jp):
        jp_data = ctypes.cast(jp, ctypes.POINTER(SJobParmsData))
        self.m_jp = SJobParmsData.from_buffer_copy(jp_data.contents)
        return '' #"machineXResolution = {0:.4f} machineYResolution = {0:.4f}".format(self.m_jp.machineXResolution, self.m_jp.machineYResolution)

    def startJob (self, Data):
        #sj_data = ctypes.cast(Data, ctypes.POINTER(SStartJobDataData))
        return ''
    def endJob (self, Data):
        #ej_data = ctypes.cast(Data, ctypes.POINTER(SEndJobData))
        return ''
        
    def startContour (self, Data):
        #sc_data = ctypes.cast(Data, ctypes.POINTER(SStartContourData))
        return ''
    def endContour (self, Data):
        #ec_data = ctypes.cast(Data, ctypes.POINTER(SEndContourData))
        return ''

    def startArc (self, Data):
        #sa_data = ctypes.cast(Data, ctypes.POINTER(SStartArcData))
        return ''
    def drawArc (self, Data):
        #da_data = ctypes.cast(Data, ctypes.POINTER(SDrawArcData))
        return ''
    def endArc (self, Data):
        #ea_data = ctypes.cast(Data, ctypes.POINTER(SEndArcData))
        return ''

    def drawXYZ (self, Data):
        #dxyz_data = ctypes.cast(Data, ctypes.POINTER(SDrawXYZData))
        return ''

    def toolChange (self, Data):
        #tc_data = ctypes.cast(Data, ctypes.POINTER(SToolChangeData))
        return ''
    def setPlunge (self, Data):
        #sp_data = ctypes.cast(Data, ctypes.POINTER(SSetPlungeData))
        return ''
    def setFeed (self, Data):
        #sf_data = ctypes.cast(Data, ctypes.POINTER(SSetFeedData))
        return ''
    def setSpin (self, Data):
        #ss_data = ctypes.cast(Data, ctypes.POINTER(SSetSpinData))
        return ''
    def dwell (self, Data):
        #sd_data = ctypes.cast(Data, ctypes.POINTER(SDwellData))
        return ''
    
    def message(self, Data):
        #msg_data = ctypes.cast(Data, ctypes.POINTER(SMessageData))
        return ''

    def drillType(self, Data):
        #dt_data = ctypes.cast(Data, ctypes.POINTER(SDrillTypeData))
        return ''
    def setMachinePlane(self, Data):
        #sm_data = ctypes.cast(Data, ctypes.POINTER(SSetMachinePlaneData))
        return ''
    def rollFeed(self, Data):
        #rf_data = ctypes.cast(Data, ctypes.POINTER(SRollFeedData))
        return ''
        
    def processCommand(self, idCmd, Data):
        match idCmd:
            case CommandType.iddInitDrivers:
                return self.initDrivers(Data)
                
            case CommandType.iddJobParms:
                return self.jobParams(Data)
            
            case CommandType.iddStartJob:
                return self.startJob(Data)
            case CommandType.iddEndJob:
                return self.endJob(Data)
                
            case CommandType.iddStartContour:
                return self.startContour(Data)
            case CommandType.iddEndContour:
                return self.endContour(Data)
                
            case CommandType.iddStartArc:
                return self.startArc(Data)
            case CommandType.iddDrawArc:
                return self.drawArc(Data)
            case CommandType.iddEndArc:
                return self.endArc(Data)

            case CommandType.iddDrawXYZ:
                return self.drawXYZ(Data)
                
            case CommandType.iddToolChange:
                return self.toolChange(Data)
            case CommandType.iddSetPlunge:
                return self.setPlunge(Data)
            case CommandType.iddSetFeed:
                return self.setFeed(Data)
            case CommandType.iddSetSpin:
                return self.setSpin(Data)
            case CommandType.iddDwell:
                return self.dwell(Data)
                    
            case CommandType.iddMessage:
                return self.message(Data)
        
            case CommandType.iddDrillType:
                return self.drillType(Data)
            case CommandType.iddSetMachinePlane:
                return self.setMachinePlane(Data)
            case CommandType.iddRollFeed:
                return self.rollFeed(Data)
            
        return ''