#format python
"""
   MoinMoin Processor to create Simple ASCII UML Diagrams 
   release 0.2.20040202
   Senya Basin, 1998-2004
"""
import re,sys,string,pprint
USAGE =""" UML2ASCII Usage:
   {{{#!uml2ascii [Options]
      Diagram DDL
   }}}
----------------------------------------------------------- 
   DDL Sample:
       [Box 1] -> [Box2] -(3,1)- [Box 3]
       [Box 4|...two liner...] -> --(2,)-- [Box 5.hvc=///]

   Box format:   [pos_x, pos_y, text, attr]
    pos_x      - column position of the box (optional, auto-incremented by default)
    pos_y      - row position of the box (optional, auto-incremented with new-line by default)
    text       - text to display in the box. Required. Use "|" for multi-line text. Use "|-|" for divider.
    attr       - Optional attributes:
                 hvc=... : use alternative characters: Horizontal, Vertical and Corner.
    Box samples: [Box1] [Class Point|-|int getX()|int getY()|void setX(int)|void setY(int)] [1,5,Box at cell 1:5]

   Link format:  START_CHAR -* FROM_TO -* END_CHAR
    START_CHAR - starting character. Required. 
    END_CHAR   - ending character. Required. 
    FROM_TO    - (id_from, id_to). Optional. From previous to next by default.
    id_from    - Box ID from which the links starts. Optional. Previous box by default.
    id_to      - Box ID to which the link goes. Optional. Next box by default.
    Link samples: -> <-> -- <> -(2,5)-> -(,7)->

   Options:
    fg:color   - diagram color
    dx:value   - minimum horizontal gap
    dy:value   - minimum vertical gap
    nn:        - print box IDs
"""
H_CHAR = '-'
V_CHAR = '|'
C_CHAR = '+'
class UMLAsciiDiagram:
    def __init__(self):
        self.pos_x  = 0
        self.pos_y  = 0
        self.obj_id = 0
        self.tab_nx = 0
        self.tab_ny = 0
        self.boxes       = {}
        self.links_from  = {}
        self.cols_width  = {}
        self.rows_height = {}
        self.cols_start  = []
        self.rows_start  = []
        self.DEBUG = 0
        self.fgcolor = 'black'
        self.DX = 5
        self.DY = 3
        self.NN = 0

    def parse_params(s, args):
        m = re.match('.*(fgcolor|fg):(\S+).*', args, re.I)
        if m:
            s.fgcolor = m.group(2)
        m = re.match('.*DY:(\d+)\s.*', args, re.I)
        if m:
            s.DY = int(m.group(1))
        m = re.match('.*DX:(\d+)\s.*', args, re.I)
        if m:
            s.DX = int(m.group(1))
        if re.match('.*NN:.*', args, re.I):
            s.NN = 1
        if re.match('.*DEBUG:.*', args, re.I):
            s.DEBUG = 1

    def pdebug(s, *args):
        if s.DEBUG: print 'DEBUG: ', args, '<br>'

    def parse_ddl(s, args):
        re_box  = re.compile(r'\[(\d*),?(\d*),?"?(.*?)\s*?"?,?(\w*?=.*?)?\]')
        re_link = re.compile(r'(\s?[<-])[-(]*(\d*),?(\d*)[-)]*([->])\s?')

        if re.match('^\s*$', args):
            return 0

        rc = 0
        s.pos_y += 1
        s.pos_x = 0

        for tok in re.findall('\[.*?\]|[<-].*?[->]\s', args):
           #s.pdebug("parsed TOKEN:", tok)
           if re.match('\[\]', tok):
               s.pos_x += 1
               continue
            
           m = re_box.match(tok)
           if m:
               (x,y,text,attr) = m.group(1,2,3,4)
               s.add_box(x, y, text, attr)
               rc = 1
            
           m = re_link.match(tok)
           if m:
               (fr,to,start,end) = m.group(2,3,1,4)
               s.add_link(fr, to, start.strip(), end.strip())

        return rc

    def add_box(s, x='', y='', text='', attr='', id=None):

        s.obj_id = parse_int(id, s.obj_id + 1)
        s.pos_x  = parse_int(x, s.pos_x + 1)
        s.pos_y  = parse_int(y, s.pos_y)

        hvc = ''
        if attr:
            m = re.match('hvc=(...)', attr)    
            if m: hvc = m.group(1)
            m = re.match('id=(\d+)', attr)    
            if m: s.obj_id = m.group(1)
        
        s.pdebug('BOX: x=', s.pos_x, '; y=', s.pos_y,
                 "; text='",text,"'; hvc=",hvc,"; id=",s.obj_id)

        (width, lines) = make_textbox(text, hvc)
        height = len(lines)
        s.boxes[s.obj_id] = (s.obj_id, s.pos_x, s.pos_y, text, attr, width, height, lines)

        if not s.cols_width.has_key(s.pos_x) or width > s.cols_width[s.pos_x]:
            s.cols_width[s.pos_x] = width

        if not s.rows_height.has_key(s.pos_y) or height > s.rows_height[s.pos_y]:
            s.rows_height[s.pos_y] = height

    def add_link(s, from_id='', to_id='', start=H_CHAR, end=H_CHAR, attr=None):
        from_id = parse_int(from_id, s.obj_id)
        to_id   = parse_int(to_id, s.obj_id + 1)

        if not s.links_from.has_key(from_id):
            s.links_from[from_id] = []

        links = s.links_from[from_id]
        links.append([from_id, to_id, start, end, attr])
        s.pdebug ("LINK: from=",from_id," to=",to_id," link=\""+start+end+"\" attr=",attr)

    def define_geometry(s):
        s.tab_nx = len(s.cols_width.keys())
        s.tab_ny = len(s.rows_height.keys())

        for i in range(s.tab_nx + 1):
            s.cols_start.append(0)

        for i in range(2, s.tab_nx + 1):
            s.cols_start[i] = s.cols_start[i-1] + s.cols_width[i-1] + s.DX;

        for i in range(s.tab_ny + 1):
            s.rows_start.append(0)

        for i in range(2, s.tab_ny + 1):
            s.rows_start[i] = s.rows_start[i-1] + s.rows_height[i-1] + s.DY;

        return s.tab_nx * s.tab_ny

    def draw_box(s, screen, box_id):
        (box_id, tx, ty, text, attr, width, height, lines) = s.boxes[box_id]
        for j in range(height):
            yy = s.rows_start[ty] + j
            for i in range(width):
                xx = s.cols_start[tx] + i
                screen[yy][xx] = lines[j][i]
        if s.NN:
            sid = repr(box_id)
            for i in range(len(sid)):
                screen[s.rows_start[ty]][s.cols_start[tx] + i] = sid[i]

    def draw_hline (s, screen, x1, x2, y, c_start, c_end):
        for x in range(x1, x2):
            screen[y][x] = H_CHAR
            
        if c_start: screen[y][x1] = c_start
        if c_end: screen[y][x2-1] = c_end

    def draw_h2line (s, screen, x1, x2, y, c_start, c_end):
        s.draw_hline(screen, x1, x2+2, y+1, c_start, c_end)
        screen[y][x1]     = V_CHAR
        screen[y+1][x1]   = C_CHAR
        screen[y][x2+1]   = V_CHAR
        screen[y+1][x2+1] = C_CHAR

    def draw_vline (s, screen, x, y1, y2, c_vert=V_CHAR):
        for y in range(y1, y2):
            screen[y][x] = c_vert

    def draw_link(s, screen, link):
        (fr, to, start, end, attr) = link

        (box_id1, tx1, ty1, undef, undef, w1, h1, undef) = s.boxes[fr]
        if not box_id1:
            return

        (box_id2, tx2, ty2, undef, undef, w2, h2, undef) = s.boxes[to]
        if not box_id2:
            return

        if ( (ty1 - ty2) == 0):
            if (abs(tx2 - tx1) == 1):
                if (tx1 < tx2):
                    x1 = s.cols_start[tx1] + w1
                    x2 = s.cols_start[tx2]
                else:
                    x1 = s.cols_start[tx2] + w1
                    x2 = s.cols_start[tx1]

                h = h2//2
                if h1 < h2: h = h1//2
                s.draw_hline(screen, x1, x2, s.rows_start[ty1] + h, start, end)
            
            elif (abs(tx2 - tx1) > 1):
                if (tx1 < tx2):
                    x1 = s.cols_start[tx1] + w1//2
                    x2 = s.cols_start[tx2] + w2//2 - 1
                else:
                    x1 = s.cols_start[tx2] + w2//2
                    x2 = s.cols_start[tx1] + w1//2 - 1

                h = h1
                if h1 < h2: h2
                s.draw_h2line(screen, x1, x2, s.rows_start[ty1] + h, start, end)

        elif ((tx1 - tx2)==0 and abs(ty2 - ty1)==1):
            if (ty1 < ty2):
                y1 = s.rows_start[ty1] + s.rows_height[ty1]
                y2 = s.rows_start[ty2]
            else:
                y1 = s.rows_start[ty2] + s.rows_height[ty2]
                y2 = s.rows_start[ty1]

            w = w2//2
            if w1 < w2: w = w1//2
            s.draw_vline(screen, s.cols_start[tx1] + w, y1, y2)

        else:
            if (tx1 < tx2):
                x1 = s.cols_start[tx1] + w1
                x2 = s.cols_start[tx2]
            else:
                x1 = s.cols_start[tx2] + w1
                x2 = s.cols_start[tx1]
            
            if (ty1 < ty2):
                y1 = s.rows_start[ty1] + h1//2
                y2 = s.rows_start[ty2] + h2//2
            else:
                y1 = s.rows_start[ty2] + h2//2
                y2 = s.rows_start[ty1] + h1//2

            w = s.DX//2
            s.draw_hline(screen, x1, x1 + w, y1, start, H_CHAR)
            s.draw_vline(screen, x1 + w, y1, y2)
            s.draw_hline(screen, x1 + w, x2, y2, H_CHAR, end)

    def draw_diagram(s):
        screen_w = s.cols_start[s.tab_nx] + s.cols_width[s.tab_nx]
        screen_h = s.rows_start[s.tab_ny] + s.rows_height[s.tab_ny]
        screen = []
        while screen_h > 0:
            screen.append(map(lambda x: ' ', range(screen_w)))
            screen_h -= 1

        for links in s.links_from.values():
            for link in links:
                s.draw_link(screen, link)

        for box_id in  s.boxes.keys():
            s.draw_box(screen, box_id)

        return print_screen(screen)    

#################################################################
def make_line(l, hch=H_CHAR, cch=C_CHAR):
    sline = cch
    while l > 2:
        l, sline = l-1, sline + hch
    return sline + cch
    
def frame_text (text, vch=V_CHAR):
        
    lines = text.split(V_CHAR);
    
    smax = 0
    for line in lines:
        if len(line) > smax: smax = len(line) 
        
    for i in range(len(lines)):
        ltmp = vch + ' ' + lines[i].ljust(smax) + ' ' + vch
        if re.match('\s*\-+\s*', lines[i]):
            lines[i] = ltmp.replace(' ','-')
        else:    
            lines[i] = ltmp
        
    return (smax + 4, lines)
        
def make_textbox (text, symbols=''):
    if not symbols: symbols = H_CHAR + V_CHAR + C_CHAR
    hchar, vchar, cchar = symbols

    (width, lines) = frame_text(text, vchar);

    sline = make_line(width, hchar, cchar);
    lines.insert(0, sline)
    lines.append(sline)
    return (width, lines);


def parse_int(val, dflt):
    if val:
        return int(val)
    else:
        return int(dflt)

def print_screen(screen):
    rc = ''
    for j in range(len(screen)):
        for i in range(len(screen[j])):
            rc += screen[j][i]
        rc += '\n'
    return rc    

#################################################################
def process(request, formatter, lines):
    try:
        ad = UMLAsciiDiagram()
        ad.parse_params(lines[0])
        del lines[0]
        
        rc = 0
        for line in lines:
            rc += ad.parse_ddl(line)

        if (rc > 0):
            ad.define_geometry()
            sb = ad.draw_diagram()
        else:
            sb = string.join(lines, '\n')
            sb = USAGE + sb
    except:
        raise

    sys.stdout.write(formatter.rawHTML('<font color=\"' + ad.fgcolor + '\">\n'))
    sys.stdout.write(formatter.rawHTML('<PRE class=code>'))
    sys.stdout.write(formatter.rawHTML(sb))
    sys.stdout.write(formatter.rawHTML('</PRE></font>'))
    return
