#! /usr/bin/python3
# Test program for module {path}
# Last edited on 2021-02-18 21:15:36 by jstolfi

import path
import move 
import contact 
import hacks
import example_path
import job_parms
import rn
import pyx
import sys
from math import sqrt,sin,cos,pi

parms = job_parms.typical()
wdfill = parms['solid_raster_width']

def test_make_empty():
  sys.stderr.write("--- testing {make_empty} -----------------\n")

  p00 = (1,1)

  ph0 = path.make_empty(p00)

  assert path.nelems(ph0) == 0
  assert path.nelems(path.rev(ph0)) == 0
  assert path.pini(ph0) == p00
  assert path.pfin(ph0) == p00
  
  mvx = move.make(p00, p00, wdfill, parms)
  assert path.find(ph0, mvx) == None

  path.validate(ph0)
  path.validate(path.rev(ph0))

def test_from_move():
  sys.stderr.write("--- testing {from_move} -----------------\n")

  p10 = (1,2)
  p11 = (2,3)

  ph1 = path.from_move(p10, p11, wdfill, parms)
  assert path.nelems(ph1) == 1
  mv10 = path.elem(ph1, 0)
  assert not move.is_jump(mv10)
  assert path.pini(ph1) == p10
  assert path.pfin(ph1) == p11
  assert path.elem(path.rev(ph1), 0) == move.rev(mv10)
  assert path.pini(path.rev(ph1)) == p11
  assert path.pfin(path.rev(ph1)) == p10
  
  assert path.find(ph1, mv10) == 0
  assert path.find(ph1, move.rev(mv10)) == 0
  assert path.find(path.rev(ph1), mv10) == 0

  mvx = move.make((0,0), (1,1), wdfill, parms)
  assert path.find(ph1, mvx) == None

  path.validate(ph1)
  path.validate(path.rev(ph1))

def test_from_moves(): 
  sys.stderr.write("--- testing {from_moves} -----------------\n")

  p20 = (2,2)
  p21 = (3,3)
  p22 = (2,4)
  p23 = (1,3)

  wd1 = parms['solid_raster_width']
  wd2 = parms['contour_trace_width']

  mv201 = move.make(p20, p21, wd1,  parms)
  mv212 = move.make(p21, p22,   0,  parms)
  mv232 = move.make(p23, p22, wd2,  parms)

  ph2 = path.from_moves((mv201, mv212, move.rev(mv232)))

  assert path.nelems(ph2) == 3
  omv0 = path.elem(ph2, 0)
  omv1 = path.elem(ph2, 1)
  omv2 = path.elem(ph2, 2)
  assert (move.unpack(omv0)) == (mv201, 0)
  assert (move.unpack(omv1)) == (mv212, 0)
  assert (move.unpack(omv2)) == (mv232, 1)
  assert not move.is_jump(omv0)
  assert move.is_jump(omv1)
  assert not move.is_jump(omv2)
  assert path.pini(ph2) == p20
  assert path.pfin(ph2) == p23
  assert path.elem(path.rev(ph2), 0) == move.rev(omv2)
  assert path.elem(path.rev(ph2), 1) == move.rev(omv1)
  assert path.elem(path.rev(ph2), 2) == move.rev(omv0)
  assert path.pini(path.rev(ph2)) == p23
  assert path.pfin(path.rev(ph2)) == p20

  mvx = move.make((0,0), (1,1), wdfill, parms)
  assert path.find(ph2, mvx) == None

  path.validate(ph2)
  path.validate(path.rev(ph2))

def test_from_points():
  sys.stderr.write("--- testing {from_points} -----------------\n")

  p30 = (4,3)
  p31 = (3,2)
  p32 = (2,3)

  ph3 = path.from_points((p30, ( p31, p32, ),), wdfill, parms)
  assert path.nelems(ph3) == 2
  mv30 = path.elem(ph3, 0)
  mv31 = path.elem(ph3, 1)
  assert move.is_jump(mv30)
  assert not move.is_jump(mv31)
  assert path.pini(ph3) == p30
  assert path.pfin(ph3) == p32
  assert path.elem(path.rev(ph3), 0) == move.rev(mv31)
  assert path.elem(path.rev(ph3), 1) == move.rev(mv30)
  assert path.pini(path.rev(ph3)) == p32
  assert path.pfin(path.rev(ph3)) == p30

  path.validate(ph3)
  path.validate(path.rev(ph3))

def test_concat():
  sys.stderr.write("--- testing {concat} -----------------\n")

  p40a = (1, 2)
  p41a = (2, 1)
  p42a = (3, 1)
  p43a = (4, 2)

  p40b = (4, 2 + 1.5*wdfill)
  p41b = (3, 3)

  p40c = p41b

  p40d = (1, 2 + 1.5*wdfill)
  p41d = p41b

  ph4a = path.from_points( ((p40a, p41a, p42a, p43a,),), wdfill, parms)
  ph4b = path.from_points( ((p40b, p41b,),), wdfill, parms)
  ph4c = path.from_points( ((p40c,),), wdfill, parms)
  ph4d = path.from_points( ((p40d, p41d,),), wdfill, parms)

  for jumps in False, True:
    ph4 = path.concat((ph4a, ph4b, ph4c, path.rev(ph4d)), jumps, parms)
    # sys.stderr.write("ph4 = %s\n" % str(ph4))
    n4 = path.nelems(ph4)
    assert n4 == 6 # One connecing jump between {ph4a} and {ph4b}.
    mv40 = path.elem(ph4, 0)
    mv41 = path.elem(ph4, 1)
    mv42 = path.elem(ph4, 2)
    mv43 = path.elem(ph4, 3)
    mv44 = path.elem(ph4, 4)
    mv45 = path.elem(ph4, 5)

    assert not move.is_jump(mv40) # p40a -- p41a
    assert mv40 == path.elem(ph4a,0)

    assert not move.is_jump(mv41) # p41a -- p42a
    assert mv41 == path.elem(ph4a,1)

    assert not move.is_jump(mv42) # p42a -- p43a
    assert mv42 == path.elem(ph4a,2)

    assert move.is_jump(mv43) == jumps # Connector

    assert not move.is_jump(mv44) # p40b -- p41b
    assert mv44 == path.elem(ph4b,0)

    assert not move.is_jump(mv45) # p40b -- p41b
    assert mv45 == move.rev(path.elem(ph4d,0))

    assert path.pini(ph4) == p40a
    assert path.pfin(ph4) == p40d
    assert path.pini(path.rev(ph4)) == p40d
    assert path.pfin(path.rev(ph4)) == p40a
    for k in range(n4):
      assert path.elem(path.rev(ph4), k) == move.rev(path.elem(ph4, n4-1-k))

    n4 = path.nelems(ph4)
    for k in range(n4):
      omvk = path.elem(ph4, k)
      assert path.find(ph4, omvk) == k
      assert path.find(ph4, move.rev(omvk)) == k
      assert path.find(path.rev(ph4), omvk) == n4-1-k
      assert path.find(path.rev(ph4), move.rev(omvk)) == n4-1-k

    path.validate(ph4)
    path.validate(path.rev(ph4))
  return
  # ----------------------------------------------------------------------

def test_displace():
  sys.stderr.write("--- testing {displace} -----------------\n")

  p20 = (2,2)
  p21 = (3,3)
  p22 = (2,4)
  p23 = (1,3)

  wd1 = parms['solid_raster_width']
  wd2 = parms['contour_trace_width']

  mv201 = move.make(p20, p21, wd1,  parms)
  mv212 = move.make(p21, p22,   0,  parms)
  mv223 = move.make(p22, p23, wd2,  parms)

  ph2 = path.from_moves((mv201, mv212, mv223))

  ang2r = pi/3
  v2r = (5,7)
  ph2r =  path.displace(ph2, ang2r, v2r, parms)
  n2r = path.nelems(ph2r) 
  assert n2r == 3
  for k in range(n2r):
    mv2k = path.elem(ph2, k)
    p2k = move.pini(mv2k)
    q2k = move.pfin(mv2k)
    p2kr = rn.add(rn.rotate2(p2k, ang2r), v2r)
    q2kr = rn.add(rn.rotate2(q2k, ang2r), v2r)
    mv2rk = path.elem(ph2r,k)
    p2rk = move.pini(mv2rk)
    q2rk = move.pfin(mv2rk)
    assert rn.dist(p2rk, p2kr) < 1.0e-8
    assert rn.dist(q2rk, q2kr) < 1.0e-8

  path.validate(ph2r)
  path.validate(path.rev(ph2r))

def test_orient_unpack():
  sys.stderr.write("--- testing {orient, path.unpack} -----------------\n")

  def check_orient_unpack(ph):
    for dr in range(4):
      pht, drt = path.unpack(path.orient(ph, dr))
      assert pht == pht
      assert drt == dr % 2
      phb, drb = path.unpack(path.orient(path.rev(pht), dr))
      assert phb == pht
      assert drb == (dr + 1) % 2

      path.validate(pht)
      path.validate(path.rev(pht))

  pa1 = (1,1)
  pa2 = (2,2)
  pa3 = (3,1)
  pa4 = (4,3)
  pa5 = (5,1)
  pa6 = (6,2)
  pa7 = (7,1)

  pha = path.from_points(((pa1, pa2, pa3,), (pa4, pa5,), (pa6, pa7,)), wdfill, parms)
  check_orient_unpack(pha)

def do_plot_bbox(c, oph, dp, wdbox):
  # Plots the bounding box of oriented path {oph} displaced by {dp}.
  B = path.bbox(oph)
  dpm = rn.add(dp, B[0])
  szxm = B[1][0] - B[0][0]
  szym = B[1][1] - B[0][1]
  hacks.plot_frame(c, pyx.color.rgb.blue, wdbox, dpm, szxm, szym, 0.0)
  return
  # ----------------------------------------------------------------------

def do_path_plot_layer(c, dp, oph, axes, dots, arrows, matter):
  # Plots on the {pyx} canvas {c} the path {oph} displaced by {dp}
  # using multiple calls of {plot_layer}.

  # Colors:
  cmatter = pyx.color.rgb(0.850, 0.800, 0.750)  # Material footprint color.
  ctraces = pyx.color.rgb(0.950, 0.250, 0.000) # Nominal trace area color.
  caxes =   pyx.color.rgb(0.6*ctraces.r, 0.6*ctraces.g, 0.6*ctraces.b) # Color of trace axes, dots, arrows.
  cjumps =  pyx.color.rgb.black  # Color of jump axes, dots, arrows.

  # Dimensions relative to nominal trace widths:
  rmatter = 1.13; # Material footprint.
  rtraces = 0.80; # Trace sausage width.

  # Absolute dimensions (mm):
  waxes =   0.05*wdfill;  # Axes of traces.
  wdots =   2.5*waxes;  # Dots at ends of moves.
  szarrows = 6*waxes      # Size of arrows. 

  for layer in range(4):
    if layer == 0:
      # plots the estimate of actual material:
      path.plot_layer\
        ( c, oph, dp, jmp=False, \
          clr=cmatter, rtraces=rmatter, waxes=0, 
          dashed=False, wdots=0, szarrows=None
        )
    elif layer == 1:
      # plots the nominal trace material:
      path.plot_layer \
        ( c, oph, dp, jmp=False,
          clr=ctraces, rtraces=rtraces, waxes=0, 
          dashed=False, wdots=0, szarrows=None
        )
    elif layer == 2:
      # Plots the trace axis:
      trc_waxes = waxes if axes else 0
      trc_wdots = wdots if dots else 0
      trc_szarrows = szarrows if arrows else 0
      path.plot_layer \
        ( c, oph, dp, jmp=False, 
          clr=caxes, rtraces = 0, waxes=trc_waxes, 
          dashed=False, wdots=trc_wdots, szarrows=trc_szarrows
        )
    elif layer == 3:
      # Plots the jump:
      jmp_waxes = waxes;
      jmp_wdots = wdots;
      jmp_szarrows = szarrows
      path.plot_layer \
        ( c, oph, dp, jmp=True, 
          clr=cjumps, rtraces = 0, waxes=jmp_waxes, 
          dashed=True, wdots=jmp_wdots, szarrows=jmp_szarrows
        )
    if layer == 0:
      # Plot the path's bounding box over the matter shadow:
      wdbox = 0.5*waxes
      do_plot_bbox(c, oph, dp, wdbox)    

def do_path_plot_standard(c, dp, oph, axes, dots, arrows, matter):
  # Plots the path {oph} shifted by {dp} using {path.plot_standard}.

  ctraces =  pyx.color.rgb(0.050, 0.850, 0.000) # Nominal trace area color.

  waxes = 0.05*wdfill
  path.plot_standard(c, oph, dp, None, ctraces, waxes, axes, dots, arrows, matter)
  # Plot the path's bounding box over everything:
  wdbox = 0.5*waxes
  do_plot_bbox(c, oph, dp, wdbox)    

def test_plot_layer_standard():
  sys.stderr.write("--- testing {plot_layer,plot_standard} -----------------\n")

  def check_plot_simple():
    # Generates plots for a simple path with various options,
    # plotting with {plot_standard} and mutiple {plot_layer}.
    # Puts all in a single EPS figure. The file names will 
    # be tests/out/path_TST_simple.{ext}"

    szx = 6
    szy = 2 + 3*wdfill

    c = pyx.canvas.canvas()
    pyx.unit.set(uscale=2.0, wscale=2.0, vscale=2.0)
    hacks.plot_frame(c, pyx.color.rgb.white, 0.30, None, 2*szx, 8*szy, -0.15)

    oph = example_path.simple(parms) 
    Y = 0
    matter = True
    for axes in False, True:
      for dots in False, True:
        for arrows in False, True:

          title = "ax%d dt%d ar%d" % (int(axes),int(dots), int(arrows))

          # Plot with {plot_standard}:
          dp0 = ( 0, Y )
          pt0 = rn.add(dp0, (0.5, 0.5))
          c.text(pt0[0], pt0[1], "STD " + title)
          do_path_plot_standard(c, dp0, oph, axes, dots, arrows, matter)
          
          # Plot with {plot_layer}:
          dp1 = ( szx, Y)
          pt1 = rn.add(dp1, (0.5, 0.5))
          c.text(pt1[0], pt1[1], "BYL " + title)
          do_path_plot_layer(c, dp1, oph, axes, dots, arrows, matter)
          
          Y = Y + szy
  
    hacks.write_plot(c, "tests/out/path_TST_plot_simple")

  def check_plot_gearloose():
    # Generates plots for the {example_path.gearloose()} path, using
    # {plot_standard} and mutiple {plot_layer}. The file names will be
    # tests/out/path_TST_gearloose_{proc}_zz{Z}.{ext}" where {proc} is
    # "STD" or "BYL" and {Z} is the {zigzag} option (0 or 1).

    R = 10
    szx = 2*R+2
    szy = 2*R+2
    dp = (0,0)

    matter = False
    axes = False
    dots = True
    arrows = False

    for zz in True, False:
      oph = example_path.gearloose(R, zz, parms)
      for byl in False, True:

        c = pyx.canvas.canvas()
        pyx.unit.set(uscale=0.5, wscale=0.5, vscale=0.5)
        hacks.plot_frame(c, pyx.color.rgb.white, 0.30, dp, szx, szy, -0.15)

        if byl:
          do_path_plot_standard(c, (0,0), oph, axes, dots, arrows, matter)
          name = "STD"
        else:
          do_path_plot_layer(c, (0,0), oph, axes, dots, arrows, matter)
          name = "BYL"
    
        name = name + ("_zz%d" % int(zz))
        hacks.write_plot(c, "tests/out/path_TST_plot_gearloose_"  + name)
        
  check_plot_simple()
  check_plot_gearloose()

def test_extime_tini_tfin():
  sys.stderr.write("--- testing {extime, path.tini, path.tfin} -----------------\n")

  def check_path_times(label, ph):
    sys.stderr.write("=== testing times of %s ===\n" % label)
    tpha = path.extime(ph)
    tphb = 0
    for k in range(path.nelems(ph)):
      omvk = path.elem(ph, k)
      tinik = path.tini(ph, k)
      tfink = path.tfin(ph, k)
      tk =  move.extime(omvk)
      # sys.stderr.write("tini[%d] = %12.8f" % (k, tinik))
      # sys.stderr.write("  extime(omv[%d]) = %12.8f" % (k, tk))
      # sys.stderr.write("  tfin[%d] = %12.8f\n" % (k, tfink))
      assert abs(tinik - tphb) < 1.0e-8
      tphb += tk
      assert abs(tfink - tphb) < 1.0e-8
    # sys.stderr.write("extime(ph) = %12.8f tphb = %12.8f\n" % (tpha, tphb))
    assert abs(tpha - tphb) < 1.0e-8

  pa1 = (1,1)
  pa2 = (2,2)
  pa3 = (3,1)
  pa4 = (4,3)
  pa5 = (5,1)
  pa6 = (6,2)
  pa7 = (7,1)

  pha = path.from_points(((pa1, pa2, pa3,), (pa4, pa5,), (pa6, pa7,)), wdfill, parms)

  check_path_times("pha", pha)
  check_path_times("rev(pha)", path.rev(pha))

test_make_empty()
test_from_move()
test_from_moves() 
test_from_points()
test_concat()
test_displace()
test_orient_unpack()
test_extime_tini_tfin()
test_plot_layer_standard()
