#! /usr/bin/python3
# Test program for module {path}
# Last edited on 2021-06-03 00:27:38 by jstolfi

import path
import path_hp
import path_example
import contour_example
import move 
import move_parms
import contact 
import block
import hacks
import palette
import job_parms
import rn
import pyx
import sys
from math import sqrt, sin, cos, floor, ceil, inf, nan, pi

parms = job_parms.typical_js()
parms['solid_raster_width'] = 1.0
parms['contour_trace_width'] = 0.5

mp_jump = move_parms.make_for_jumps(parms)
mp_cont = move_parms.make_for_contours(parms)
mp_fill = move_parms.make_for_fillings(parms)

wd_fill = move_parms.width(mp_fill)

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, mp_fill)
  assert path.find_move(ph0, mvx) == None

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

  return
  # ----------------------------------------------------------------------

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

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

  ph1 = path.from_move(p10, p11, mp_fill)
  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_move(ph1, mv10) == 0
  assert path.find_move(ph1, move.rev(mv10)) == 0
  assert path.find_move(path.rev(ph1), mv10) == 0

  mvx = move.make((0,0), (1,1), mp_fill)
  assert path.find_move(ph1, mvx) == None

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

  return
  # ----------------------------------------------------------------------

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

  p0 = (2,2)
  p1 = (3,3)
  p2 = (2,4)
  p3 = (1,3)
  
  mv01 = move.make(p0, p1, mp_fill)
  mv12 = move.make(p1, p2, mp_jump)
  mv32 = move.make(p3, p2, mp_fill)

  ph = path.from_moves((mv01, mv12, move.rev(mv32)))

  assert path.nelems(ph) == 3
  omv0 = path.elem(ph, 0)
  omv1 = path.elem(ph, 1)
  omv = path.elem(ph, 2)
  assert (move.unpack(omv0)) == (mv01, 0)
  assert (move.unpack(omv1)) == (mv12, 0)
  assert (move.unpack(omv)) == (mv32, 1)
  assert not move.is_jump(omv0)
  assert move.is_jump(omv1)
  assert not move.is_jump(omv)
  assert path.pini(ph) == p0
  assert path.pfin(ph) == p3
  assert path.elem(path.rev(ph), 0) == move.rev(omv)
  assert path.elem(path.rev(ph), 1) == move.rev(omv1)
  assert path.elem(path.rev(ph), 2) == move.rev(omv0)
  assert path.pini(path.rev(ph)) == p3
  assert path.pfin(path.rev(ph)) == p0

  mvx = move.make((0,0), (1,1), mp_fill)
  assert path.find_move(ph, mvx) == None

  path.validate(ph)
  path.validate(path.rev(ph))

  return
  # ----------------------------------------------------------------------

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

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

  ph3 = path.from_points((p30, ( p31, p32, ),), mp_fill, mp_jump)
  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))

  assert path.get_name(ph3) == "P?"
  path.set_name(ph3, "Tao")
  assert path.get_name(ph3) == "Tao"
  assert path.get_name(path.rev(ph3)) == "~Tao"
  
  ph4 = path.from_points((p30, p32, ), mp_fill, mp_jump)

  sys.stderr.write("  ... applying {path.tag_names} ...\n")
  path.tag_names([ph3, ph4], "Tag.")
  assert path.get_name(ph3) == "Tag.Tao"
  assert path.get_name(path.rev(ph3)) == "~Tag.Tao"
  assert path.get_name(ph4) == "Tag.P?"

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

  OPHS = path_example.misc_C(mp_fill, mp_jump)
  assert type(OPHS) is list
  assert isinstance(OPHS[0], path.Path)
  assert len(OPHS) == 5

  for use_jumps in False, True:
    for use_links in False, True:
      if not (use_jumps and use_links):
        paths = (OPHS[0], OPHS[1], OPHS[2], path.rev(OPHS[4]), path.rev(OPHS[3]))
        ph = path.concat(paths, use_jumps, use_links, mp_jump)
        # sys.stderr.write("ph = %s\n" % str(ph))
        nmv = path.nelems(ph)
        assert nmv == 8 # Three connecting jumps or links.
        mv0 = path.elem(ph, 0)
        mv1 = path.elem(ph, 1) # Jump, original
        mv2 = path.elem(ph, 2)
        mv3 = path.elem(ph, 3) # Connector
        mv4 = path.elem(ph, 4)
        mv5 = path.elem(ph, 5) # Connector
        mv6 = path.elem(ph, 6) # Connector
        mv7 = path.elem(ph, 7) # Zero-length

        assert not move.is_jump(mv0) # p0a -- p1a
        assert mv0 == path.elem(OPHS[0],0)

        assert move.is_jump(mv1) # Jump p1a -- p2a 
        assert mv1 == path.elem(OPHS[0],1)

        assert not move.is_jump(mv2) # p2a -- p3a
        assert mv2 == path.elem(OPHS[0],2)

        if use_jumps:
          # Connectors must be jumps:
          assert move.is_jump(mv3)
        elif use_links:
          # Connectors must be links:
          assert not move.is_jump(mv3) 
        else:
          # Connectors may be jumps or links:
          pass

        assert not move.is_jump(mv4)
        assert mv4 == path.elem(OPHS[1],0)

        assert move.is_jump(mv5) # Because of params incompatibility.
        assert move.is_jump(mv6) # Because of params incompatibility.

        assert not move.is_jump(mv7) # Zero-length p0b -- p1b
        assert mv7 == move.rev(path.elem(OPHS[3],0))

        assert path.pini(ph) == move.pini(mv0)
        assert path.pfin(ph) == move.pfin(mv7)
        assert path.pini(path.rev(ph)) == move.pfin(mv7)
        assert path.pfin(path.rev(ph)) == move.pini(mv0)
        for imv in range(nmv):
          assert path.elem(path.rev(ph), imv) == move.rev(path.elem(ph, nmv-1-imv))
          omvk = path.elem(ph, imv)
          assert path.find_move(ph, omvk) == imv
          assert path.find_move(ph, move.rev(omvk)) == imv
          assert path.find_move(path.rev(ph), omvk) == nmv-1-imv
          assert path.find_move(path.rev(ph), move.rev(omvk)) == nmv-1-imv

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

def test_find_nearest_point():
  sys.stderr.write("--- testing {find_nearest_point} ---\n")
  
  def test_path(oph):
    nmv = path.nelems(oph)
    PTS = [ move.pini(path.elem(oph,i)) for i in range(nmv) ]
    if nmv == 0:
      sys.stderr.write("  ... empty path ...\n")
      kmax = 0
    elif path.pini(oph) == path.pfin(oph):
      sys.stderr.write("  ... closed path ...\n")
      kmax = nmv - 1
    else:
      sys.stderr.write("  ... open path ...\n")
      PTS.append(path.pfin(oph))
      kmax = nmv
    for k in range(len(PTS)):
      pt = rn.mix(1.0, PTS[k], 0.001, (sin(k), cos(k)))
      d = rn.dist(pt, PTS[k]) 
      kx, dx = path.find_nearest_point(oph, pt)
      assert kx <= kmax
      ptx = move.pini(path.elem(oph, kx)) if kx < nmv else path.pfin(oph)
      # sys.stderr.write("k =  %d pt =  ( %20.16f %20.16f ) d =  %20.16f\n" % (k,pt[0],pt[1],d))
      # sys.stderr.write("kx = %d ptx = ( %20.16f %20.16f ) dx = %20.16f\n" % (kx,ptx[0],ptx[1],dx))
      assert kx == k
      assert dx == rn.dist(pt, PTS[k])
    return 
    # ......................................................................
    
  OPHS = path_example.misc_J(mp_cont,mp_fill)
  
  test_path(OPHS[0])
  test_path(OPHS[1])
  return
  # ----------------------------------------------------------------------
  
def test_mean_projections():
  sys.stderr.write("--- testing {mean_projections} ---\n")
  
  ph = path_example.misc_A(mp_fill, mp_cont, mp_jump)
  assert isinstance(ph, path.Path)

  angle = pi/3
  xdir = rn.rotate2((1, 0), angle)
  ydir = rn.rotate2((0, 1), angle)
  
  m = rn.mix(0.5, path.pini(ph), 0.5, path.pfin(ph))
  xm_ex = rn.dot(m, xdir)
  ym_ex = rn.dot(m, ydir)
  
  for oph in ph, path.rev(ph):
    xm_cp, ym_cp = path.mean_projections(oph, xdir, ydir)
    assert abs(xm_cp - xm_ex) < 1.0e-14
    assert abs(ym_cp - ym_ex) < 1.0e-14

    xm_cp, ym_cp = path.mean_projections(oph, None, ydir)
    assert xm_cp == None
    assert abs(ym_cp - ym_ex) < 1.0e-14

    xm_cp, ym_cp = path.mean_projections(oph, xdir, None)
    assert abs(xm_cp - xm_ex) < 1.0e-14
    assert ym_cp == None
    
  return
  # ----------------------------------------------------------------------

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

  ph = path_example.misc_A(mp_fill, mp_cont, mp_jump)
  assert isinstance(ph, path.Path)

  angr = pi/3
  vr = (5,7)
  phr =  path.displace(ph, angr, vr)
  nr = path.nelems(phr) 
  assert nr == 3
  for imv in range(nr):
    mvk = path.elem(ph, imv)
    pk, qk = move.endpoints(mvk)
    pkr = rn.add(rn.rotate2(pk, angr), vr)
    qkr = rn.add(rn.rotate2(qk, angr), vr)
    mvrk = path.elem(phr,imv)
    prk, qrk = move.endpoints(mvrk)
    assert rn.dist(prk, pkr) < 1.0e-8
    assert rn.dist(qrk, qkr) < 1.0e-8

  path.validate(phr)
  path.validate(path.rev(phr))

  return
  # ----------------------------------------------------------------------

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

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

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

  PHS = path_example.misc_C(mp_fill, mp_jump)
  assert type(PHS) is list
  assert isinstance(PHS[0], path.Path)
  pha = PHS[0]
  check_orient_unpack(pha)

  return
  # ----------------------------------------------------------------------

def test_name():
  sys.stderr.write("--- testing {set_name,get_name,tag_names} ---\n");
  
  def gname(var, oph):
    # Returns {get_name(oph)}, printing to {stderr}.
    xms = path.get_name(oph)
    sys.stderr.write("  get_name(%s) = %s\n" % (var,str(xms)))
    return xms
    # ............................................................

  p1 = (1,1)
  p2 = (2,3)
  p3 = (4,5)
  p4 = (6,7)
  pha = path.from_points(( (p1,), (p2,p3,p4), ), mp_fill,mp_jump) 
  xms = gname("pha", pha)
  assert xms == "P?"
  xms = gname("~pha", path.rev(pha))
  assert xms == "~P?"

  path.set_name(pha, "Tao")
  xms = gname("pha", pha)
  assert xms == "Tao"
  xms = gname("~pha", path.rev(pha))
  assert xms == "~Tao"

  return
  # ----------------------------------------------------------------------

def test_show():

  sys.stderr.write("--- testing {show,show_list} ---\n")

  OPHS, TRS, JMS = path_example.misc_D(mp_fill, mp_jump)
  nph = len(OPHS)
  
  sys.stderr.write("  ... {show} ...\n")
  wna = 5
  wnm = 5
  for k in range(nph):
    moves = (k % 2 == 1)
    for oph in OPHS[k], path.rev(OPHS[k]):
      sys.stderr.write("[")
      path.show(sys.stderr, oph, moves, 4, wna, wnm)
      sys.stderr.write("]\n")
      wna = wna + 2
      wnm = max(wnm - 1, 0)
  sys.stderr.write("]\n")
  
  sys.stderr.write("  ... {show_list} ...\n")
  path.show_list(sys.stderr, OPHS, True, 2)
  return 
  # ----------------------------------------------------------------------

def do_plot_bbox(c, OPHS, dp, wdbox):
  # Plots the bounding box of oriented paths in the list {OPHS} displaced by {dp}.
  B = path.bbox(OPHS)
  hacks.plot_frame(c, pyx.color.rgb.blue, wdbox, dp, B, 0.0)
  return
  # ----------------------------------------------------------------------

def test_incremental():
  
  sys.stderr.write("--- testing {move_to, finish} ---\n")

  MVS = []
  
  PHS = []
  
  n0 = 30; R0 =  4
  n1 = 40; R1 = 10
  ctr = ( R1 + 1, R1 + 1 )
  p = None
  for i in range(n0+1):
    a = 2*pi*i/n0
    q = rn.mix(1, ctr, R0, ( cos(a), sin(a) + 0.15*sin(5*a) ))
    p = path.move_to(MVS, p, q, mp_cont, "C0")
  path.finish(PHS, MVS, "C0")
  assert len(MVS) == 0
  assert len(PHS) == 1
  path.validate(PHS[0])
  assert path.nelems(PHS[0]) == n0
  
  p = None
  for i in range(n1+1):
    a = 2*pi*i/n1
    q = rn.mix(1, ctr, R1*(1 + 0.10*cos(5*a)), ( cos(a), sin(a) ))
    p = path.move_to(MVS, p, q, mp_cont, "C1")
  path.finish(PHS, MVS, "C1")
  assert len(MVS) == 0
  assert len(PHS) == 2
  path.validate(PHS[1])
  assert path.nelems(PHS[1]) == n1
  
  CLRS = hacks.trace_colors(len(PHS))
  rwd = 0.80
  wd_axes = 0.05*wd_fill;  # Axes of traces.
  grid = True
  deco = True

  fname = "tests/out/path_TST_incremental"
  path.plot_to_files(fname, PHS, CLRS, rwd, wd_axes, grid, deco)
  
  return
  # ----------------------------------------------------------------------

def test_contours():
  sys.stderr.write("--- testing {compute_contour_nesting,inner_contours,outer_contours,shift_contour} ---\n")
  
  sys.stderr.write("  ... two nested circles ...\n")

  OCRS0 = contour_example.contours_B(mp_cont)
  
  path.compute_contour_nesting(OCRS0)
  phc0 = OCRS0[0]
  phc1 = OCRS0[1]
  assert frozenset(path.inner_contours(phc0)) == frozenset()
  assert frozenset(path.inner_contours(phc1)) == frozenset((phc0,))
  assert frozenset(path.outer_contours(phc0)) == frozenset((phc1,))
  assert frozenset(path.outer_contours(phc1)) == frozenset()
  
  phc0r = path.rev(phc0)
  phc0s = path.shift_contour(phc0r, 3)
  assert path.nelems(phc0r) == path.nelems(phc0)
  assert path.pini(phc0s) == move.pini(path.elem(phc0r, 3))
  assert path.pfin(phc0s) == path.pini(phc0s)
  nmv0 = path.nelems(phc0)
  for i in range(nmv0):
    assert path.elem(phc0s, i) == path.elem(phc0r, (i + 3) % nmv0)

  sys.stderr.write("  ... ten paths, complex nesting ...\n")

  OCRS, PTSS = contour_example.contours_A(mp_cont)
 
  ncr = len(OCRS)
  
  # Check for correct points:
  for i in range(ncr):
    ocri = OCRS[i]
    PTSi = PTSS[i]
    PTSx = [ move.pini(path.elem(ocri, k)) for k in range(path.nelems(ocri)) ]
    assert tuple(PTSx) == tuple(PTSi)

  # Check containment relation:
  # Shorter names for the contours:
  assert ncr == 10
  A,B,C,D,E,F,G,H,I,J = OCRS

  assert set(path.inner_contours(A)) == set((C,D,F,G,H,I,J,))
  assert set(path.inner_contours(B)) == set((E,))
  assert set(path.inner_contours(C)) == set((F,G,))
  assert set(path.inner_contours(D)) == set((H,I,J,))
  assert set(path.inner_contours(E)) == set()
  assert set(path.inner_contours(F)) == set()
  assert set(path.inner_contours(G)) == set()
  assert set(path.inner_contours(H)) == set((I,J,))
  assert set(path.inner_contours(I)) == set()
  assert set(path.inner_contours(J)) == set()

  assert set(path.outer_contours(A)) == set()
  assert set(path.outer_contours(B)) == set()
  assert set(path.outer_contours(C)) == set((A,))
  assert set(path.outer_contours(D)) == set((A,))
  assert set(path.outer_contours(E)) == set((B,))
  assert set(path.outer_contours(F)) == set((A,C,))
  assert set(path.outer_contours(G)) == set((A,C,))
  assert set(path.outer_contours(H)) == set((A,D,))
  assert set(path.outer_contours(I)) == set((A,D,H,))
  assert set(path.outer_contours(J)) == set((A,D,H,))
  
  assert path.contour_nesting(A,A) == 0
  assert path.contour_nesting(A,B) == 0
  assert path.contour_nesting(A,C) == +1
  assert path.contour_nesting(A,D) == +1
  assert path.contour_nesting(A,E) == 0
  assert path.contour_nesting(A,F) == +1
  assert path.contour_nesting(A,G) == +1
  assert path.contour_nesting(A,H) == +1
  assert path.contour_nesting(A,I) == +1
  assert path.contour_nesting(A,J) == +1
  
  assert path.contour_nesting(C,A) == -1
  assert path.contour_nesting(C,B) == 0
  assert path.contour_nesting(C,C) == 0
  assert path.contour_nesting(C,D) == 0
  assert path.contour_nesting(C,E) == 0
  assert path.contour_nesting(C,F) == +1
  assert path.contour_nesting(C,G) == +1
  assert path.contour_nesting(C,H) == 0
  assert path.contour_nesting(C,I) == 0
  assert path.contour_nesting(C,J) == 0
  
  assert path.contour_nesting(D,A) == -1
  assert path.contour_nesting(D,B) == 0
  assert path.contour_nesting(D,C) == 0
  assert path.contour_nesting(D,D) == 0
  assert path.contour_nesting(D,E) == 0
  assert path.contour_nesting(D,F) == 0
  assert path.contour_nesting(D,G) == 0
  assert path.contour_nesting(D,H) == +1
  assert path.contour_nesting(D,I) == +1
  assert path.contour_nesting(D,J) == +1
  
  assert path.contour_nesting(H,A) == -1
  assert path.contour_nesting(H,B) == 0
  assert path.contour_nesting(H,C) == 0
  assert path.contour_nesting(H,D) == -1
  assert path.contour_nesting(H,E) == 0
  assert path.contour_nesting(H,F) == 0
  assert path.contour_nesting(H,G) == 0
  assert path.contour_nesting(H,H) == 0
  assert path.contour_nesting(H,I) == +1
  assert path.contour_nesting(H,J) == +1
  
  for ocr0 in OCRS:
    for ocr1 in OCRS:
      ne01 = path.contour_nesting(ocr0,ocr1)
      ne10 = path.contour_nesting(ocr1,ocr0)
      if ocr0 == ocr1:
        assert ne01 == 0 and ne10 == 0
      else:
        assert ne01 == -ne10
   
  CLRS = hacks.trace_colors(len(OCRS))
  rwd = 0.80
  wd_axes = 0.05*wd_fill;  # Axes of traces.
  grid = True
  deco = True

  fname = "tests/out/path_TST_contours"
  path.plot_to_files(fname, OCRS, CLRS, rwd, wd_axes, grid, deco)
  
  return
  # ----------------------------------------------------------------------
 
def test_plot_basic():
  # Generates a plots showing the same set of oriented paths with
  # various options, plotting with {plot_standard} and mutiple calls of
  # {plot_layer}. Puts all in a single EPS figure. The file name will be
  # tests/out/path_TST_plot_basic.{ext}"

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

  OPHS = path_example.misc_C(mp_fill, mp_jump)
  nph = len(OPHS)

  CLRS = hacks.trace_colors(nph)
  nclr = len(CLRS)

  def pick_colors(k):
    # Returns the colors for trace sausages and axes of path {OPHS[k]}.
    if nclr == 1:
      ctraces = CLRS[0]
    else:
      ctraces = CLRS[k]
    caxes =   pyx.color.rgb(0.6*ctraces.r, 0.6*ctraces.g, 0.6*ctraces.b) # Color of trace axis, dots, arrow.
    return ctraces, caxes

  # Dimensions
  rwd = 0.80; # Trace sausage width.
  wd_axes =   0.05*wd_fill;  # Axes of traces.
  wd_dots =   2.5*wd_axes;  # Dots at ends of moves.
  sz_arrows = 8*wd_axes      # Size of arrows. 
  wd_box = 0.5*wd_axes

  def do_test_plot_layer(c, dp, axes, dots, arrows, matter):
    # Plots on the {pyx} canvas {c} the oriented paths in the list {OPHS}
    # displaced by {dp} using multiple calls of {plot_layer}.

    # Colors:
    cmatter = pyx.color.rgb(0.850, 0.800, 0.750)  # Material footprint color.
    cjumps =  pyx.color.rgb.black  # Color of jump axes, dots, arrows.

    # Dimensions relative to nominal trace widths:
    rwd_matter = 1.13; # Material footprint.

    for layer in range(4):
      for k in range(nph):
        oph = OPHS[k]
        ctraces, caxes = pick_colors(k)
        if layer == 0:
          # plots the estimate of actual material:
          path.plot_layer\
            ( c, oph, dp, jmp=False, \
              clr=cmatter, rwd=rwd_matter, wd=0, 
              dashed=False, wd_dots=0, sz_arrows=None
            )
        elif layer == 1:
          # plots the nominal trace material:
          path.plot_layer \
            ( c, oph, dp, jmp=False,
              clr=ctraces, rwd=rwd, wd=0, 
              dashed=False, wd_dots=0, sz_arrows=None
            )
        elif layer == 2:
          # Plots the trace axis:
          t_wd = wd_axes if axes else 0
          t_wd_dots = wd_dots if dots else 0
          t_sz_arrows = sz_arrows if arrows else 0
          path.plot_layer \
            ( c, oph, dp, jmp=False, 
              clr=caxes, rwd=0, wd=t_wd, 
              dashed=False, wd_dots=t_wd_dots, sz_arrows=t_sz_arrows
            )
        elif layer == 3:
          # Plots the jump:
          j_wd = wd_axes;
          j_wd_dots = wd_dots;
          j_sz_arrows = sz_arrows
          path.plot_layer \
            ( c, oph, dp, jmp=True, 
              clr=cjumps, rwd=0, wd=j_wd, 
              dashed=True, wd_dots=j_wd_dots, sz_arrows=j_sz_arrows
            )
        if layer == 0:
          # Plot the path's bounding box over the matter shadow:
          wd_box = 0.5*wd_axes
          do_plot_bbox(c, [oph,], dp, wd_box)    
    return

  def do_test_plot_standard(c, dp, axes, dots, arrows, matter):
    # Plots the oriented paths in the list {OPHS} shifted by {dp} using
    # {path.plot_standard} with no layers.

    path.plot_standard(c, OPHS, dp, None, CLRS, rwd, wd_axes, axes, dots, arrows, matter)
    # Plot the path's bounding box over everything:
    do_plot_bbox(c, OPHS, dp, wd_box)  
    return

  # Get the bounding box {pbox} of one plot:
  B = path.bbox(OPHS)
  
  dp = None
  
  c, szx, szy = hacks.make_canvas(B, dp, True, True, 4, 4)

  # Plot with various options:
  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 = ((2*arrows+0)*szx, Y )
        pt0 = rn.add(dp0, (0.5, 0.5))
        c.text(pt0[0], pt0[1], "STD " + title)
        do_test_plot_standard(c, dp0, axes, dots, arrows, matter)

        # Plot with {plot_layer}:
        dp1 = ((2*arrows+1)*szx, Y)
        pt1 = rn.add(dp1, (0.5, 0.5))
        c.text(pt1[0], pt1[1], "BYL " + title)
        do_test_plot_layer(c, dp1, axes, dots, arrows, matter)

      Y = Y + szy

  hacks.write_plot(c, "tests/out/path_TST_plot_basic")
  return
  # ----------------------------------------------------------------------

def test_plot_single():
  # Generates a plots showing the same set of oriented paths with
  # various options, plotting with {plot_standard} and mutiple calls of
  # {plot_layer}. Puts all in a single EPS figure. The file name will be
  # tests/out/path_TST_plot_basic.{ext}"

  sys.stderr.write("--- testing {plot_single} ---\n")

  oph = path_example.misc_B(mp_fill, mp_jump)

  # Get the bounding box {pbox} of one plot:
  B = path.bbox([oph,])
  
  dp = (0,0)

  c, szx, szy = hacks.make_canvas(B, dp, True, True, 1, 2)

  clr = pyx.color.rgb(0.850, 0.050, 0.000)

  dpk = dp
  path.plot_single(c, oph, dpk, False, clr); dpk = rn.add(dpk, (0, szy))
  path.plot_single(c, oph, dpk, True, None); dpk = rn.add(dpk, (0, szy))

  hacks.write_plot(c, "tests/out/path_TST_plot_single")
  return
  # ----------------------------------------------------------------------

def test_plot_to_files(grid, deco):
  # Generates a plots showing a set of oriented paths plotted with {plot_to_file}.
  # The file name will be tests/out/path_TST_plot_to_files_deco{deco}.{ext}"

  sys.stderr.write("--- testing {plot_to_files} deco = %s---\n" % deco)
  
  tag = "grid%s_deco%s" % ("FT"[grid],"FT"[deco])

  OPHS, TRS, JMS = path_example.misc_E(mp_fill, mp_jump)
  nph = len(OPHS)

  CLRS = hacks.trace_colors(nph)
  rwd = 0.80
  wd_axes = 0.05*wd_fill;  # Axes of traces.

  fname = "tests/out/path_TST_plot_to_files_" + tag
  path.plot_to_files(fname, OPHS, CLRS, rwd, wd_axes, grid, deco)
  return 
  # ----------------------------------------------------------------------

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)
    path.validate(ph)
    phr = path.rev(ph)
    tpha = path.extime(ph)
    tphar = path.extime(phr)
    assert abs(tpha - tphar) < 1.0e-8
    nmv = path.nelems(ph)
    for imv in range(nmv):
      omvk = path.elem(ph,imv)
      mvk, drk = move.unpack(omvk)
      tinik = path.tini(ph, imv)
      tfink = path.tfin(ph, imv)
      # sys.stderr.write("tini[%d] = %12.8f" % (imv, tinik))
      # sys.stderr.write("  tfin[%d] = %12.8f\n" % (imv, tfink))
      omvkr = path.elem(phr, nmv-1-imv)
      mvkr, drkr = move.unpack(omvkr)
      assert mvk == mvkr
      assert drk == 1-drkr
      tinikr = path.tini(path.rev(ph), nmv-1-imv)
      tfinkr = path.tfin(path.rev(ph), nmv-1-imv)
      assert abs((tfink + tinikr) - tpha) < 1.0e-8
      assert abs((tinik + tfinkr) - tpha) < 1.0e-8
      if move.is_jump(omvk):
        assert (tfink - tinik) >= move.extime(omvk) - 1.0e-8
      else:
        assert abs((tfink - tinik) - move.extime(omvk)) < 1.0e-8
    tphb = path.tfin(ph, nmv-1)
    tphbr = path.tfin(phr, nmv-1)
    # sys.stderr.write("extime(ph) = %12.8f tphb = %12.8f\n" % (tpha, tphb))
    assert abs(tphb - tphbr) < 1.0e-8
    assert abs(tpha - tphb) < 1.0e-8

    assert abs(path.tini(ph,nmv) - tpha) < 1.0e-8
    assert path.tfin(ph,-1) == 0

  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,)), mp_fill, mp_jump)

  check_path_times("pha", pha)
  check_path_times("rev(pha)", path.rev(pha))
  return
  # ----------------------------------------------------------------------

test_plot_single()
test_incremental()
test_contours()

test_make_empty()
test_from_move()
test_from_moves() 
test_from_points()
test_concat()
test_displace()
test_orient_unpack()
test_find_nearest_point()
test_mean_projections()
test_extime_tini_tfin()
test_name()
test_show()
test_plot_basic()
for grid in False, True:
  for deco in False, True:
    test_plot_to_files(grid, deco)

