# -*- coding: utf-8 -*-

import re
import numpy

vsplitter = re.compile('[ ,]')
vextractor = re.compile('(?P<value>[^;\n\r\aA-DF-Za-df-z]*?)(?P<terminator>[;\n\r\a]+)', re.S+re.M) 

class HPGLParser(object):
    def __init__(self, data=None):
        self.str_terminator = chr(0x03)
        self.IN()
        if data:
            self.parse(data)

    def parse(self, data):
        self.data = data
        self.idx = 0
        self.IN()        
        while self.idx<len(data):
            while data[self.idx] in [';','\n','\r', '\a', ' ']:
                self.idx += 1
            if data[self.idx] == chr(0x03):
                self.ESC()
            else:
                cmd = data[self.idx: self.idx+2].upper()
                self.idx += 2
                getattr(self, cmd)()
            while self.idx<len(data) and data[self.idx] in [';','\n','\r', '\a', ' ']:
                self.idx += 1

    def user_to_abs(self, pos):
        """
        converts a position (x, y) given in user units to absolute
        units
        """
        return pos #TODO
        
    def extract_value(self, raw=False):
        m = vextractor.match(self.data[self.idx:])
        if m:
            val = m.group("value")
            self.idx = self.idx + len(val) + len(m.group("terminator"))
            val = vsplitter.split(val)
            if not raw:
                val = [eval(x) for x in val if x.strip()]
            return val
        return []

    def extract_string(self):
        idx2 = self.data.find(self.str_terminator, self.idx)
        val = self.data[self.idx: idx2]
        self.idx = idx2+1
        return val

    def polyline(self, values):
        if (len(values)%2) == 1:
            # this is a syntax error in the HPGL data
            values = values[:-1]
        values = numpy.array(values).reshape(-1, 2)
        # TODO use scaling            
        # do plots 
        values = numpy.r_[[self.pos], values] #add self.pos as 1st value
        if self.plot_mode == 'relative':
            values = values.cumsum(axis=0)
        if self.symbol_mode:
            self.plot_symbols(values[1:])
        if self.pen_state == "down":
            self.plot_lines(values)
        self.pos = values[-1]

    def plot_string(self, s):
        pass
    
    def get_window_for_paper(self):
        return 0,100,0,100
    def get_scaling_points_for_paper(self):
        return 0,100,0,100
        
    # effective plot methods; to be defined in subclasses (backends)
    def plot_symbols(self, points):
        pass
    def plot_lines(self, points):
        pass
        
    # HPGL-related methods
    def ESC(self):
        #print "ESC"
        self.idx += 1

    def OE(self):
        """ Output Error """
        pass
        
    def EC(self):
        """? what's this """
        values = self.extract_value()

    def OO(self):
        """idem"""
        pass

    def OP(self):
        """idem"""
        pass

    def VS(self):
        """idem"""
        pass
        
        
    def DF(self):
        """ Default """
        self.pen = 0
        self.pen_width = 1 # 1/300 inch
        self.pos = 0,0
        self.char_set = "std"
        self.plot_mode = 'absolute'
        self.char_direction = 1,0
        self.line_type = 0 # 'solid'
        self.pattern_len = 4 #percentage
        self.window = self.get_window_for_paper()
        self.char_size = 0.75, 1.5 #percentages
        self.symbol_mode = False
        self.tick_len = 0.5, 0.5 # %
        self.std_char_set = 0
        self.alt_char_set = 0
        self.selected_char_set = 0
        self.char_slant = 0 # degrees
        self.scale = None
        self.str_terminator = chr(0x03)
        self.chord_ang = 5 #degrees
        self.fill_type = 'bidirectionnal', 1
        self.fill_distance = 1 # %
        self.fill_slant = 0 # degrees
        self.pen_thickness = 0.3 # mm

    def IN(self):
        """ Initialize """
        self.DF()
        self.pen_state = "up"
        self.rotation = 0 #degrees
        self.scaling_points = self.get_scaling_points_for_paper()

    def IP(self):
        """ Input Scale Point"""
        values = self.extract_value()
        if len(values) == 0:
            self.scaling_points = self.get_scaling_points_for_paper()
        elif len(values) == 2:
            p1x, p1y, p2x, p2y = self.scaling_points
            dx, dy = p2x-p1x, p2y-p1y
            x, y = values
            self.scaling = (x, y, x+dx, y+dy)
        elif len(values) == 4:
            self.values = tuple(values)
            
    def SC(self):
        """ Scale """
        values = self.extract_value()
        if len(values) == 4:
            self.scale = tuple(values)
        else:
            self.scale = None

    def IW(self):
        values = self.extract_value()
        if len(values) == 0:
            self.window = self.get_window_for_paper()
        elif len(value) == 4:            
            self.window = tuple(values)

    def RO(self):
        values = self.extract_value()
        if len(values) == 0:
            self.rotation = 0
        elif len(values) == 1:
            self.rotation = values[0]

    def PG(self):
        pass

    def PU(self):
        """ Pen Up"""
        self.pen_state = "up"        
        values = self.extract_value()
        self.polyline(values)

    def PD(self):
        """ Pen Down """
        self.pen_state = "down"        
        values = self.extract_value()
        self.polyline(values)

    def PA(self):
        """ Plot Absolute """
        self.plot_mode = "absolute"        
        values = self.extract_value()
        self.polyline(values)

    def PR(self):
        """ Plot Relative """
        self.plot_mode = "relative"        
        values = self.extract_value()
        self.polyline(values)

    def AA(self):
        """ Arc Absolute """
        values = self.extract_value()
        if len(values) in [3, 4]:
            x, y, qc = values[:3]
            if len(values)==4:
                qd = values[-1]
            else:
                qd = self.chord_ang
            # TODO : plot arc
            print "plotting an arc"
    def AR(self):
        """ Arc Relative """
        values = self.extract_value()
        # TODO

    def CI(self):
        """ Circle Plot"""
        values = self.extract_value()
        # TODO

    def EA(self):
        """Edge Rectangle Absolute"""
        values = self.extract_value()
        # TODO

    def ER(self):
        """Edge Rectangle Relative"""
        values = self.extract_value()
        # TODO

    def EW(self):
        """ Edge Wedge """
        values = self.extract_value()
        # TODO

    def RA(self):
        """ Fill Rectangle Absolute """
        values = self.extract_value()
        # TODO

    def RR(self):
        """ Fill Rectangle Relative """
        values = self.extract_value()
        # TODO

    def WG(self):
        """ Fill Wedge """
        values = self.extract_value()
        # TODO

    def FT(self):
        """ Fill Type """
        values = self.extract_value()
        # TODO

    def LT(self):
        """ Line Type """
        values = self.extract_value()

        if len(values)==0:
            self.line_type = 0 #'solid'
        else:
            self.line_type = values[0]
            if len(values)>1:
                self.pattern_len = values[1]

    def PW(self):
        """ Pen Width """
        values = self.extract_value()
        if len(values) == 1:
            self.pen_thickness = values[0]

    def SM(self):
        """ Symbol Mode """
        values = self.extract_value()
        if len(values) == 0:
            self.symbol_mode = False
        elif len(values) == 1:
            self.symbol_mode = values[0]

    def SP(self):
        """ Select Pen """
        values = self.extract_value()
        if len(values) == 1:
            self.pen = values[0]

    def TL(self):
        """ Tick Len """
        values = self.extract_value()
        # TODO

    def XT(self):
        """ X-axis Tick """
        # TODO
        print "X ticks"

    def YT(self):
        """ X-axis Tick """
        # TODO
        print "Y ticks"

    def PT(self):
        """ Pen Thickness Select """
        values = self.extract_value()
        if len(values) == 0:
            self.pen_thickness = 0.3
        elif len(values) == 1:
            self.pen_thickness = values[0]

    def CS(self):
        """ Standard Character Set """
        values = self.extract_value()
        if len(values) == 0:
            self.std_char_set = 0
        elif len(values) == 1:
            self.std_char_set = values[0]

    def CA(self):
        """ Alternate Character Set """
        values = self.extract_value()
        if len(values) == 0:
            self.alt_char_set = 0
        elif len(values) == 1:
            self.alt_char_set = values[0]
        
    def SS(self):
        """ Select Standard Character Set """
        self.char_set = "std"

    def SA(self):
        """ Select Alternate Character Set """
        self.char_set = "alt"

    def DT(self):
        """ Define Label Terminator """
        values = self.extract_value(raw=True)
        if len(values) == 0:
            self.str_terminator = chr(0x03)
        elif len(values) == 1:
            self.str_terminator = values[0]

    def LB(self):
        """ Character Plot """
        values = self.extract_string()
        self.plot_string(values)
        x, y = self.pos
        values = values.split('\n')
        if self.char_size == "absolute":
            x += len(values[-1]) * self.char_width *1016/2.54
            y += (len(values)-1) * self.char_height *1016/2.54
        else:
            x0, x1, y0, y1 = self.scale
            dx = x1-x0
            dy = y1-y0
            x += len(values[-1]) * self.char_width / 100.0 * dx
            y += (len(values)-1) * self.char_height / 100.0 * dy
            
        #print "LB pos[%s] %s -> %s"%(repr(values), self.pos, (x,y))
        self.pos = [x, y]

    def get_char_size(self):
        if self.char_size == "absolute":
            x = self.char_width *1016/2.54
            y = self.char_height *1016/2.54
        else:
            x0, x1, y0, y1 = self.scale
            dx = x1-x0
            dy = y1-y0
            x = self.char_width / 100.0 * dx
            y = self.char_height / 100.0 * dy
        return x, y
    
    def DI(self):
        """ Absolute Direction """
        values = self.extract_value()
        if len(values) == 0:
            self.char_direction = 1.0, 0.0
        elif len(values) == 2:
            self.char_direction = values

    def DR(self):
        """ Relative Direction """
        values = self.extract_value()
        if len(values) == 0:
            self.char_direction = 1.0, 0.0
        elif len(values) == 2:
            # TODO : compute as percentages
            self.char_direction = values

    def CP(self):
        """ Character Plot """
        values = self.extract_value()
        # TODO
        if len(values) == 0:
            values = 0, 1
        x, y = self.pos
        if self.char_size == "absolute":
            x += values[0] * self.char_width *1016/2.54
            y += values[1] * self.char_height *1016/2.54
        else:
            x0, x1, y0, y1 = self.scale
            dx = x1-x0
            dy = y1-y0
            x += values[0] * self.char_width / 100.0 * dx
            y += values[1] * self.char_height / 100.0 * dy
        #print "CB pos[%s] %s -> %s"%(repr(values), self.pos, (x,y))
        self.pos = [x, y]

    def SI(self):
        """ Set Absolute Character Size """
        values = self.extract_value()
        self.char_size = "absolute"
        if len(values) == 0:
            self.char_width = 0.1879 # cm
            self.char_height = 0.2690 # cm
        elif len(values) == 2:
            self.char_width, self.char_height = values

    def SR(self):
        """ Set Relative Character Size """
        values = self.extract_value()
        self.char_size = "relative"
        if len(values) == 0:
            self.char_width = 0.75 # percentage
            self.char_height = 1.5 # id
        elif len(values) == 2:
            self.char_width, self.char_height = values
        
    def SL(self):
        """ Character Slant """        
        values = self.extract_value()
        # TODO

    def UC(self):
        """ User Defined Character """
        values = self.extract_value()
        # TODO
        
if __name__ == '__main__':
    import sys
    data = open(sys.argv[1]).read()
    
    p = HPGLParser(data)
    
