 drawMessageText = (ctx, x, y, body) ->
   width = ctx.measureText(body).width
   ctx.fillStyle = "white"
-  ctx.fillRect(x - width / 2, y + 3, width, 10)
+  ctx.fillRect(x - width / 2, y , width, 12)
   ctx.fillStyle = "black"
   ctx.font = "12px Sans-Serif"
   ctx.textAlign = "center"
   ctx.textBaseline = "top"
   ctx.fillText(body, x, y)
 
-getMessageRect = (ctx, obj) ->
-  result = new Object()
-  pt = getIntersectionPoint(obj.from.order, obj.order)
-  result.x1 = pt.x
-  result.y1 = pt.y
-  pt = getIntersectionPoint(obj.to.order, obj.order)
-  result.x2 = pt.x
-  result.y2 = pt.y
-  result.centerX = getMiddlePos(result.x1, result.x2)
-  result.centerY = getMiddlePos(result.y1, result.y2)
-  return result
-
-getMiddlePos = (a, b) ->
-  return a + (b - a) / 2
-
-getIntersectionPoint = (instanceOrder, messageOrder) ->
-  result = new Object()
-  rect = getInstanceRect(instanceOrder)
-  result.x = rect.centerX
-  result.y = rect.y + rect.height + (messageOrder + 1) * MESSSAGE_OFFSET
-  return result
-
 # ------------------------------
 
 drawInstances = (ctx, instances, bottom) ->
-  drawInstance(ctx, obj, bottom) for obj in instances
-
-drawInstance = (ctx, obj, bottom) ->
-  rect = getInstanceRect(obj.order)
-  drawInstanceRect(ctx, rect)
-  drawInstanceText(ctx, rect, obj.name)
-  drawInstanceLine(ctx, rect, bottom)
-
-getInstanceRect = (order) ->
-  result = new Object()
-  result.x = order * (INSTANCE_WIDTH + INSTANCE_OFFSET) + CANVAS_PADDING
-  result.y = 0 + CANVAS_PADDING
-  result.width = INSTANCE_WIDTH
-  result.height = INSTANCE_HEIGHT
-  result.centerX = result.x + result.width / 2
-  result.centerY = result.y + result.height / 2
-  return result
+  for obj in instances
+    drawInstanceRect(ctx, obj.rect)
+    drawInstanceText(ctx, obj.rect, obj.name)
+    drawInstanceLine(ctx, obj.rect, bottom)
+  return
 
 drawInstanceRect = (ctx, rect) ->
   ctx.strokeRect(rect.x, rect.y, rect.width, rect.height)

MESSSAGE_OFFSET = 40
MESSSAGE_BODY_OFFSET = -18
MESSSAGE_SELF_WIDTH = 27.5
MESSSAGE_SELF_HEIGHT = 17
MESSSAGE_ARROW_WIDTH = 5
MESSSAGE_ARROW_HEIGHT = -8

INSTANCE_OFFSET = 40
INSTANCE_WIDTH = 70
INSTANCE_HEIGHT = 30


drawObjects = (instances, messages) ->
  canvas = document.getElementById("diagram")
  return unless canvas
  ctx = canvas.getContext("2d")
  ctx.font = "12px Sans-Serif"
  addRectToInstances(ctx, instances, messages)
  addRectToMessages(ctx, instances, messages)
  canvas.height = calcCanvasHeight(messages)
  ctx.font = "12px Sans-Serif"
  canvas.width = calcCanvasWidth(ctx, instances)
  fillBackground(ctx, canvas.width, canvas.height)
  drawInstances(ctx, instances, canvas.height - MESSSAGE_OFFSET - CANVAS_PADDING)
  drawMessages(ctx, messages)

addRectToInstances = (ctx, instances, messages) ->
  first = _.min(instances, (obj) -> obj.order)
  x = CANVAS_PADDING + getInstanceMargin(ctx, instances, messages, first)
  for obj in instances
    obj.rect = createInstanceRect(ctx, x, obj)
    x += Math.round(obj.rect.width + INSTANCE_OFFSET)

getInstanceMargin = (ctx, instances, messages, instance) ->
  return 0 unless instance?
  
  # TODO noteBodyMargin
  normalBodyMargin = getMaxNormalBodyMargin(ctx, instance)
  selfBodyMargin = getMaxSelfBodyMargin(ctx, instance)
  bodyMargin = Math.max(normalBodyMargin, selfBodyMargin)
  
  nameWidth1 = roundInstanceWidth(ctx, instance.name)
  nameMargin = nameWidth1 / 2
  gap = Math.round(bodyMargin - nameMargin)
  return gap if gap > 0
  
  return 0

getMaxSelfBodyMargin = (ctx, instance) ->
  message = _.max(messages, (obj) -> getRelatedSelfBodyWidth(ctx, instance, obj))
  return 0 unless isValidMessage(message)
  return 0 unless isSelfMessage(message)
  return 0 if isNote(message)
  return 0 unless message.from is instance
  
  bodyWidth = ctx.measureText(message.body).width
  return bodyWidth / 2

getRelatedSelfBodyWidth = (ctx, instance, message) ->
  return 0 unless message.from is instance
  return 0 unless isSelfMessage(message)
  
  bodyWidth = ctx.measureText(message.body).width
  return bodyWidth / 2

getMaxNormalBodyMargin = (ctx, instance1) ->
  message = _.max(messages, (obj) -> getRelatedBodyWidth(ctx, instance1, obj))
  return 0 unless isValidMessage(message)
  
  instance2 = message.to
  return 0 unless instance2?
  
  nameWidth1 = roundInstanceWidth(ctx, instance1.name)
  nameWidth2 = roundInstanceWidth(ctx, instance2.name)
  
  distance = nameWidth1 / 2 + nameWidth2 / 2 + INSTANCE_OFFSET
  bodyWidth = ctx.measureText(message.body).width
  return bodyWidth / 2 - distance / 2

getRelatedBodyWidth = (ctx, instance1, message) ->
  return 0 if isSelfMessage(message)
  
  if message.from is instance1
    instance2 = message.to
  else if message.to is instance1
    instance2 = message.from
  else
    return 0
  return 0 unless instance2?
  
  nameWidth1 = roundInstanceWidth(ctx, instance1.name)
  nameWidth2 = roundInstanceWidth(ctx, instance2.name)
  
  distance = nameWidth1 / 2 + nameWidth2 / 2 + INSTANCE_OFFSET
  bodyWidth = ctx.measureText(message.body).width
  return bodyWidth

createInstanceRect = (ctx, x, obj) ->
  rect = new Object()
  rect.x = x
  rect.y = CANVAS_PADDING
  rect.width = roundInstanceWidth(ctx, obj.name)
  rect.height = INSTANCE_HEIGHT
  rect.centerX = Math.round(rect.x + rect.width / 2)
  rect.centerY = Math.round(rect.y + rect.height / 2)
  return rect

roundInstanceWidth = (ctx, name) ->
  nameWidth = ctx.measureText(name).width
  return Math.max(INSTANCE_WIDTH, nameWidth + INSTANCE_PADDING)

addRectToMessages = (ctx, instances, messages) ->
  for obj in messages
    if checkPresenceOfToFromObjects(obj)
      obj.rect = createMessageRect(ctx, instances, obj)
      if isNote(obj)
        obj.note = createNote(obj.rect)
      else if isSelfMessage(obj)
        obj.selfMessage = createSelfMessage(ctx, instances, obj)
      else
        obj.normalMessage = createNormalMessage(instances, obj)

createMessageRect = (ctx, instances, obj) ->
  rect = new Object()
  pt = createIntersectionPoint(instances, obj.from.order, obj.order)
  rect.x1 = pt.x
  rect.y1 = pt.y
  pt = createIntersectionPoint(instances, obj.to.order, obj.order)
  rect.x2 = pt.x
  rect.y2 = pt.y
  rect.centerX = getMiddlePos(rect.x1, rect.x2)
  rect.centerY = getMiddlePos(rect.y1, rect.y2)
  obj.rect = rect

createIntersectionPoint = (instances, instanceOrder, messageOrder) ->
  result = new Object()
  rect = getInstanceRectByOrder(instances, instanceOrder)
  result.x = rect.centerX
  result.y = rect.y + rect.height + (messageOrder + 1) * MESSSAGE_OFFSET
  return result

getMiddlePos = (a, b) ->
  return a + (b - a) / 2

createNote = (rect) ->
  pt = new Object()
  pt.x = rect.centerX - INSTANCE_WIDTH / 2
  pt.y = rect.centerY
  note = new Object()
  note.bodyPoint = pt
  return note

createSelfMessage = (ctx, instances, obj) ->
  result = new Object()
  result.rect = createSelfMessageRect(ctx, instances, obj)
  result.headPoint = createSelfMessageHeadPoint(result.rect)
  result.bodyPoint = createSelfMessageBodyPoint(result.rect)
  return result

createSelfMessageRect = (ctx, instances, obj) ->
  result = new Object()
  pt = createIntersectionPoint(instances, obj.from.order, obj.order)
  result.x1 = pt.x
  result.y1 = pt.y
  result.x2 = pt.x + MESSSAGE_SELF_WIDTH
  result.y2 = pt.y + MESSSAGE_SELF_HEIGHT
  return result

createSelfMessageHeadPoint = (rect) ->
  pt = new Object()
  pt.x = rect.x1
  pt.y = rect.y2
  return pt

createSelfMessageBodyPoint = (rect) ->
  pt = new Object()
  pt.x = rect.x1
  pt.y = rect.y1 + MESSSAGE_BODY_OFFSET
  return pt

createMessageBodyPoint = (rect) ->
  pt = new Object()
  pt.x = rect.centerX
  pt.y = rect.centerY + MESSSAGE_BODY_OFFSET
  return pt

createNormalMessage = (instances, obj) ->
  result = new Object()
  result.headPoint = createIntersectionPoint(instances, obj.to.order, obj.order)
  result.bodyPoint = createMessageBodyPoint(obj.rect)
  return result

calcCanvasHeight = (messages) ->
  last = _.max(messages, (obj) -> obj.order)
  return unless last?
  height = INSTANCE_HEIGHT + MESSSAGE_OFFSET * (last.order + 2)
  height += CANVAS_PADDING * 2
  return height

calcCanvasWidth = (ctx, instances) ->
  last = _.max(instances, (obj) -> obj.order)
  return unless last?
  x = getInstanceMargin(ctx, instances, messages, last)
  width = CANVAS_PADDING * 2 + last.rect.x + last.rect.width + x
  return width

fillBackground = (ctx, width, height) ->
