# diagram.py: script to generate diagrams of gradint lessons
# (C) 2008 Silas S. Brown.  License: GPL

# gradint is run normally (passing any command-line arguments on)
# and then a diagram of the lesson it made is written to diagram.svg

# you can get .ps by doing: inkscape -p '> diagram.ps' diagram.svg

pixelsPerSec = 1 # initially, can be adjusted by minWidth/maxWidth
rowHeight = 20 # initially, can be adjusted by minHeight/maxHeight
maxWidth = maxHeight = minWidth = minHeight = 0 # for no limit
# minWidth = 400 ; maxWidth = 600
minHeight = 200 ; maxHeight = 400

import gradint
import math,os

# map xrange to range if it doesn't exist (Python 3)
try: xrange
except: xrange = range

min_num_secs_before_row_reuse = 25 # to make the diagram clearer

#svg=os.popen("gzip -9 > diagram.svgz","w") # TODO does this still get broken pipes ?
svg=open("diagram.svg","w") # ** TODO choose either/or ?
# TODO: need to convert to PNG etc for IE

svg.write("""<?xml version="1.0"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
  "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n""")

def str2dp(f): return "%.2f" % float(f)

def moveTo(x,y):
    global svgCurPos
    svgCurPos=x,y
def lineTo(x,y):
    global svgCurPos
    svg.write('<line x1="'+str2dp(svgCurPos[0])+'" y1="'+str2dp(svgCurPos[1]))
    svgCurPos=x,y
    svg.write('" x2="'+str2dp(svgCurPos[0])+'" y2="'+str2dp(svgCurPos[1])+'" stroke-width="1" />\n')
def rectangle(x,y,width,height,colour="white"):
    if colour=="white": stroke="black"
    else: stroke=colour
    svg.write('<rect x="'+str2dp(x)+'" y="'+str2dp(y)+'" width="'+str2dp(width)+'" height="'+str2dp(height)+'" style="stroke:'+stroke+';stroke-width:1px; fill:'+colour+';" />\n')

def Event_draw(self,startTime,pixelsPerSec,topY,height): pass
gradint.Event.draw = Event_draw

def CompositeEvent_draw(self,startTime,pixelsPerSec,topY,height):
    if len(self.eventList) > 1:
        rectangle(startTime*pixelsPerSec,topY,self.length*pixelsPerSec,height)
        topY += height*0.1
        height *= 0.8
    for i in self.eventList:
      i.draw(startTime,pixelsPerSec,topY,height)
      startTime += i.length
gradint.CompositeEvent.draw=CompositeEvent_draw

def Event_colour(self,language):
    if hasattr(self,"wordToCancel"):
      if language==gradint.firstLanguage: return "yellow" # TODO: 2nd to 3rd lang etc?
      else: return "green"
    else: return "grey" # prompts
gradint.Event.colour = Event_colour

def SampleEvent_draw(self,startTime,pixelsPerSec,topY,height):
    rectangle(startTime*pixelsPerSec,topY,self.length*pixelsPerSec,height,self.colour(gradint.languageof(self.file)))
gradint.SampleEvent.draw = SampleEvent_draw
def SynthEvent_draw(self,startTime,pixelsPerSec,topY,height):
    rectangle(startTime*pixelsPerSec,topY,self.length*pixelsPerSec,height,self.colour(self.language))
gradint.SynthEvent.draw = SynthEvent_draw

def drawCoils(leftX,topY,width,height,springLen):
  assert springLen >= width
  if springLen==width: deviation,nCoils=0,0
  else:
    t=math.sqrt(springLen*springLen-width*width)/4
    deviation=height/2.0 # maximum
    nCoils=int(math.ceil(t/deviation))
    deviation=t/nCoils
  midY=topY+height/2.0
  svg.write('<g stroke="purple">') # colour for coils
  moveTo(leftX,midY)
  for coil in xrange(nCoils):
    lineTo(leftX+(coil+.25)*width/nCoils,midY-deviation)
    lineTo(leftX+(coil+.75)*width/nCoils,midY+deviation)
  lineTo(leftX+width,midY)
  svg.write('</g>')

def drawGlue(glue,glueStart,pixelsPerSec,rowNo):
  rigidLen=max(0,glue.length-glue.plusMinus)
  springWidth=glue.length+glue.adjustment-rigidLen
  springLen=2*glue.plusMinus
  svg.write('<g stroke="blue">') # colour for the rigid part of the "spring"
  moveTo(glueStart*pixelsPerSec,(rowNo+0.5)*rowHeight)
  lineTo((glueStart+rigidLen)*pixelsPerSec,(rowNo+0.5)*rowHeight)
  svg.write('</g>')
  drawCoils((glueStart+rigidLen)*pixelsPerSec,rowNo*rowHeight+rowMargin,springWidth*pixelsPerSec,rowHeight-2*rowMargin,springLen*pixelsPerSec)

glueToDraw=[] ; eventsToDraw=[] ; rows=[]
ellipsesToDraw = []
def prepareDraw(gluedEventList):
  # rows stores max time currently shown in each row.  This function must be called in time order.
  glueStart = 0
  row = -1
  ellipseLeft = ellipseRight = -1
  for i in gluedEventList:
    startTime = i.getEventStart(glueStart)
    if row<0:
      for r in range(len(rows)):
        if (not rows[r]) or rows[r]<=startTime-min_num_secs_before_row_reuse:
          row=r ; break
    if row<0:
      row=len(rows) ; rows.append(0)
    i.event.row=row
    i.event.start = startTime
    if ellipseLeft<0: ellipseLeft = startTime
    eventsToDraw.append(i.event)
    if glueStart:
      i.glue.row=row
      i.glue.start = glueStart
      glueToDraw.append(i.glue)
    glueStart = i.getAdjustedEnd(glueStart)
    ellipseRight = glueStart
    rows[row] = glueStart
  if ellipseLeft>=0 and ellipseRight>=0: ellipsesToDraw.append((row,ellipseLeft,ellipseRight,hasattr(gluedEventList[0],"timesDone") and gluedEventList[0].timesDone))

def sgn(i):
    if i>0: return 1
    elif i<0: return -1
    else: return 0

def byFirstLen(e1,e2):
    r = e1[0].glue.length+e1[0].glue.adjustment-e2[0].glue.length-e2[0].glue.adjustment
    # but it must return int not float, so
    return sgn(r)
def byStart(e1,e2): return sgn(e1.start-e2.start)

gradint.gluedListTracker=[]
gradint.waitBeforeStart=0
gradint.main()
gradint.gluedListTracker.sort(byFirstLen)
for l in gradint.gluedListTracker: prepareDraw(l)

# Calculate height and width
num_rows = len(rows)
num_seconds = max(rows)
if maxWidth: pixelsPerSec=min(pixelsPerSec,maxWidth*1.0/num_seconds)
if minWidth: pixelsPerSec=max(pixelsPerSec,minWidth*1.0/num_seconds)
if maxHeight: rowHeight=min(rowHeight,maxHeight*1.0/num_rows)
if minHeight: rowHeight=max(rowHeight,minHeight*1.0/num_rows)
image_width = int(math.ceil(num_seconds*pixelsPerSec))
image_height = int(math.ceil(num_rows*rowHeight))
svg.write('<svg xmlns="http://www.w3.org/2000/svg"  width="'+str2dp(image_width)+'" height="'+str2dp(image_height)+'">')

rowMargin = rowHeight*0.15

for row,ellipseLeft,ellipseRight,timesDone in ellipsesToDraw:
    if timesDone: fill="#c0c0c0"
    else: fill="pink"
    svg.write('<ellipse cx="'+str2dp((ellipseLeft+ellipseRight)*pixelsPerSec/2)+'" cy="'+str2dp((row+0.5)*rowHeight)+'" rx="'+str2dp((ellipseRight-ellipseLeft)*pixelsPerSec/2)+'" ry="'+str2dp((rowHeight-rowMargin/2)/2)+'" fill="'+fill+'" stroke="#c0c0c0" />\n') # ellipse behind whole sequence might make things clearer (although it does take a little longer to draw)

for g in glueToDraw: drawGlue(g,g.start,pixelsPerSec,g.row) # before events, so the reading line goes over the top of it

eventsToDraw.sort(byStart)
x=y=None
for e in eventsToDraw:
  if not x==None:
    svg.write('<g stroke="red">') # (lines between events to show reading order, at 0.25 and 0.75 so as not to clash with glue at 0.5)
    for yOff in [-0.25*rowHeight,0.25*rowHeight]:
      moveTo(x,y+yOff)
      lineTo(e.start*pixelsPerSec,(e.row+0.5)*rowHeight+yOff)
    svg.write('</g>')
  e.draw(e.start,pixelsPerSec,e.row*rowHeight+rowMargin,rowHeight-2*rowMargin)
  x=(e.start+e.length)*pixelsPerSec
  y=(e.row+0.5)*rowHeight

svg.write("</svg>")