# Implementation of module {contact_example}
# Last edited on 2021-05-27 05:29:20 by jstolfi

import contact_example
import move
import move_parms
import path
import path_example
import block
import block_example
import contact
import job_parms
import hacks
import rn
import pyx 
from math import sqrt, sin, cos, log, exp, nan, inf, pi
import sys

parms = job_parms.typical_js()
parms['solid_raster_width'] = 1.00
parms['contour_trace_width'] = 0.50

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)
wd_cont = move_parms.width(mp_cont)

# CONTACTS

def misc_A(mp_fill):
  
  # Create a contact {ct}:
  p00 = (1,1)
  p01 = (4,1)
  mv0 = move.make(p00, p01, mp_fill)
  
  p10 = (2,2)
  p11 = (5,2)
  mv1 = move.make(p10, p11, mp_fill)
  
  q0 = (2, 1.5)
  q1 = (4, 1.5)
  ct = contact.make(q0, q1, mv0, mv1)
  contact.set_name(ct, "CA")

  return ct
  # ----------------------------------------------------------------------

def misc_B(mp_trace, mp_jump):

  # Create five paths with ten traces and five jumps:
  OPHS, TRS, JMS = path_example.misc_E(mp_trace, mp_jump)
  assert TRS != None
  
  # Make some contacts between the traces:
  q00 = (2.0, 3.5)
  q01 = (3.0, 3.5)
  ct0 = contact.make(q00, q01, TRS[0], TRS[1])

  q10 = (4.0, 3.5)
  q11 = q10
  ct1 = contact.make(q10, q11, TRS[1], TRS[2])

  q20 = (3.5, 3.0)
  q21 = (3.5, 3.0)
  ct2 = contact.make(q20, q21, TRS[0], TRS[2])

  dq3 = (1.0/sqrt(5), 0.5/sqrt(5))
  q30 = tuple(rn.sub(move.pini(TRS[3]), dq3))
  q31 = tuple(rn.add(move.pfin(TRS[2]), dq3))
  ct3 = contact.make(q30, q31, TRS[2], TRS[3])

  q40 = (5.0, 4.5)
  q41 = (5.0, 4.5)
  ct4 = contact.make(q40, q41, TRS[1], TRS[4])
  
  q50 = (3.0, 7.5)
  q51 = (4.0, 7.5)
  ct5 = contact.make(q50, q51, TRS[7], TRS[9])
  
  q60 = (5.0, 7.5)
  q61 = (6.0, 7.5)
  ct6 = contact.make(q60, q61, TRS[8], TRS[9])
  
  q70 = (3.0, 6.5)
  q71 = (4.0, 6.5)
  ct7 = contact.make(q70, q71, TRS[7], TRS[5])
  
  CTS = [ ct0, ct1, ct2, ct3, ct4, ct5, ct6, ct7, ]
  for i in range(len(CTS)): contact.set_name(CTS[i], "CB%d" % i)
   
  return CTS, OPHS, TRS
  # ----------------------------------------------------------------------
  
def misc_C(mp_trace):
  mp_trace = mp_fill
  wd = move_parms.width(mp_trace)
  nrs = [7, 3]        # Number of rasters in each path.
  plo = [None,None] # Lower left corner of bbox each path.
  sz = [ 3*wd, 5*wd]
  plo[0] = ( 2, 1 )
  plo[1] = ( plo[0][0] + sz[0] + wd, 2)
  PHS = [None, None]  # The two paths.
  for i in range(2):
    axis = i
    alt = True
    PHSi, TRSi, LJS0i, LJS1i = path_example.raster_rectangle(plo[i], axis, nrs[i], alt, sz[i], wd, mp_trace,None)
    move.tag_names(TRSi, "s%d." % i)
    move.tag_names(LJS0i, "s%d." % i)
    move.tag_names(LJS1i, "s%d." % i)
    path.tag_names(PHSi, "s%d." % i)
    PHS[i] = PHSi[0]
  szmin = 0.25
  rszmin = 0.50
  CTS = contact.from_paths(PHS[0], PHS[1], szmin, rszmin)
  for i in range(len(CTS)): contact.set_name(CTS[i], "CC%d" % i)

  return CTS, PHS
  # ----------------------------------------------------------------------

def misc_F(alt, mp_trace, mp_jump):

  ph = path_example.misc_F(alt, mp_trace, mp_jump)
  
  # Get the adjacent traces, which should be horizontal:
  if alt:
    ix = (5, 15)
  else:
    ix = (3, 9)
  mv = [None, None] # The two moves.
  plo = [None, None] # The left endpoints of the moves.
  phi = [None, None] # The right endpoints of the moves.
  for k in range(2):
    mvk, drk = move.unpack(path.elem(ph, ix[k]))
    plok = move.pini(mvk)
    phik = move.pfin(mvk)
    assert plok[1] == phik[1]
    if plok[0] > phik[0]: plok, phik = phik, plok
    mv[k] = mvk; plo[k] = plok; phi[k] = phik
  
  # Get the endpoints of the contact:
  y = (plo[0][1] + plo[1][1])/2
  xlo = max(plo[0][0], plo[1][0])
  xhi = min(phi[0][0], phi[1][0])

  ct = contact.make((xlo,y), (xhi,y), mv[0], mv[1])
  contact.set_name(ct, "CF")

  return ct, ph
  # ----------------------------------------------------------------------

# FROM PATHS AND BLOCKS

def raster_raster_contact(oph0, oph1):
  # The {X} range of the contact will be {[xlo _ xhi]}
  xlo = -inf 
  xhi = +inf
  mv = [None,None]    # Top raster traces at top of {oph0} and bottom of {oph1}.
  for k in range(2):
    ophk = (oph0, oph1)[k]
    
    # Find the top and bottom rasters {omvk_lo, omvk_hi}:
    nmvk = path.nelems(ophk)
    omvk_lo = path.elem(ophk,0)
    omvk_hi = path.elem(ophk,nmvk-1)
    if move.pini(omvk_lo)[1] > move.pini(omvk_hi)[1]:
      omvk_lo, omvk_hi = omvk_hi, omvk_lo
    
    # Now pick the relevant trace for this block
    omvk = omvk_hi if k == 0 else omvk_lo
    
    # Save the unoriented move into {mv[k]}:
    mv[k], drk = move.unpack(omvk)
    
    # Find the {X} span and intersect with current {[xlo _ xhi]}
    pk, qk = move.endpoints(mv[k])
    assert pk[1] == qk[1] # Trace must be horizontal.
    xlok = min(pk[0], qk[0])
    xhik = max(pk[0], qk[0])
    xlo = max(xlo, xlok)
    xhi = min(xhi, xhik)

  # Check coordinates:
  y0 = move.pini(mv[0])[1]; wd0 = move.width(mv[0])
  y1 = move.pini(mv[1])[1]; wd1 = move.width(mv[1])
  assert abs((y0 + wd0/2) - (y1 - wd1/2)) < 0.01*(wd0+wd1), "traces are not adjacent"
  assert xlo < xhi, "{X} randges do not overlap"
  
  # Create the contact:
  ymd = (y0 + y1 + (wd0 - wd1)/2)/2
  ct = contact.make((xlo,ymd), (xhi, ymd), mv[0], mv[1])
  return ct
  # ----------------------------------------------------------------------

def raster_raster_block_contact(bc0, bc1):
  # The {X} range of the contact will be {[xlo _ xhi]}
  xlo = -inf 
  xhi = +inf
  mv = [None,None]    # Top raster traces at top of {bc0} and bottom of {bc1}.
  for k in range(2):
    bck = (bc0, bc1)[k][0]
    # Pick some choice of the block, {bc0}  or {bc1}
    ophk = block.choice(bck, 0)
    omvk = path.elem(ophk, (bc0, bc1)[k][1])
    
    # Check if they are both traces of the same width
    wdk = move.width(omvk)
    assert wdk > 0 # Must be trace not jump.
    # Save the unoriented move into {mv[k]}:
    mv[k], drk = move.unpack(omvk)
    # Find the {X} span and intersect with current {[xlo _ xhi]}
    pk = move.pini(mv[k])
    qk = move.pfin(mv[k])
    assert pk[1] == qk[1] # Trace must be horizontal.
    xlok = min(pk[0], qk[0])
    xhik = max(pk[0], qk[0])
    xlo = max(xlo, xlok)
    xhi = min(xhi, xhik)

  # Check coordinates:
  y0 = move.pini(mv[0])[1]; wd0 = move.width(mv[0])
  y1 = move.pini(mv[1])[1]; wd1 = move.width(mv[1])

  assert abs((y0 + wd0/2) - (y1 - wd1/2)) < 0.01*(wd0+wd1), "traces are not adjacent"
  assert xlo < xhi, "{X} randges do not overlap"
  
  # Create the contact:
  ymd = (y0 + y1 + (wd0 - wd1)/2)/2
  ct = contact.make((xlo,ymd), (xhi, ymd), mv[0], mv[1])
  contact_hp.set_side_block(ct, 0, bc0)
  contact_hp.set_side_block(ct, 1, bc1)
  return ct
  # ----------------------------------------------------------------------

def multi_raster_roads_OLD(nrd, nbc, nmv, mp_fill):

  wdf = move_parms.width(mp_fill)
  
  # In multiples of {wdf}, relative to {org}:
  nx_rd = 4                             # Width of each road.
  nx_gp = 3                             # Width of gap between roads.
  ixstep_rd = nx_rd + nx_gp             # Scan-column step between roads.
  nxtot_rd = (nrd-1)*ixstep_rd + nx_rd  # Total width of roads.
  nytot_rd = nbc*nmv                    # Total height of roads.
  
  org = (2,2)   # Lower left corner of leftmost road.
  
  H0 = make_raster_block(org, 0, nxtot_rd-1,       -1,      -1, mp_fill, mp_jump)
  H1 = make_raster_block(org, 0, nxtot_rd-1, nytot_rd,nytot_rd, mp_fill, mp_jump)
  
  BCS = [ H0, H1 ]
  CTS = [] 
  for ird in range(nrd):
    ix0 = ird*ixstep_rd
    ix1 = ix0 + nx_rd - 1
    bc_prev = H0
    for ibc in range(nbc):
      iy0 = ibc*nmv
      iy1 = iy0 + nmv - 1
      bc_this = make_raster_block(org, ix0, ix1, iy0, iy1, mp_fill, mp_jump)
      BCS.append(bc_this)
      # ??? Should use {seam.from_blocks} ???
      oph_prev = block.choice(bc_prev, 0)
      oph_this = block.choice(bc_this, 0)
      ct = contact_example.raster_raster_contact(oph_prev, oph_this)
      CTS.append(ct)
      bc_prev = bc_this
    oph_prev = block.choice(bc_prev, 0)
    oph_H1 = block.choice(H1, 0)
    ct = contact_example.raster_raster_contact(oph_prev, oph_H1)
    CTS.append(ct)

  return BCS, CTS
  # ----------------------------------------------------------------------
  
def two_roads_and_islands(nmv, nmg, nis, mp_cont, mp_fill, mp_jump):

  wdc = move_parms.width(mp_cont)
  wdf = move_parms.width(mp_fill)
  
  org = (2,2)   # Lower left corner of whole thing.

  # Key dimensions and coords, in {wdf}  units, rel to {org}:
  nx_rd = 5                 # Length of rasters on the roads.
  nxy_is = 4                # Width and height of an island plus margin.
  
  nx_gp = nxy_is + 2        # Width of gap between roads.

  ixstep_rd = nx_rd + nx_gp # Scan-column increment between roads.
  ixstep_is = ixstep_rd     # Scan-column increment between island centers.
  
  assert nx_gp % 2 == 0     # To avoid half-scanlines.
  ixctr_is0 = nx_gp//2      # Scan-column of center of left island group.
  ix0_rd0 = nx_gp           # Left scan-column of left road.

  nytot_is = nis*nxy_is                       # Total height of islands plus margins.
  assert nytot_is % 2 == 0                    # To avoid half-scanlines.
  iyctr_is = max(nmv//2, nytot_is//2)         # Index of scanline at center of islands.
  iyctr_is0 = iyctr_is - (nytot_is-nxy_is)//2 # Index of scanline at center of bottom island.
  iy0_rd = iyctr_is - nmv//2                  # Index of scanline at bottom of roads. 
  iystep_is = nxy_is                          # Scanline step between centers of islands.

  # Absolute road dimensions and coords in {mm} (do not include the width of traces):
  Ric = wdf + wdc               # Radius of endpoints in island contour.
  assert Ric + wdc/2 < nxy_is*wdf/2    
  Rif = 0                       # Min inradius of island.
    
  def make_island_block(kx, ky):
    # Returns block that is a single island, specifically
    # island {ky} from bottom to top in group {kx} from left to right.
    # See {block_example.onion}.
    dx0 = (ixctr_is0 + kx*ixstep_is)*wdf
    dy0 = (iyctr_is0 + ky*iystep_is)*wdf
    ctr = rn.add(org, (dx0, dy0))
    nch = 4  # Number of choices of starting point of contour.
    bc = block_example.onion(nch, ctr, Ric, mp_cont, Rif, mp_fill, mp_jump)
    return bc

  BCS = []
  CTS = []
  
  # Compute the number {nbc} of multiraster blocks at each end of a road:
  nbc = ((nmv - 1)//(2*nmg))
  assert nbc >= 0
  # Total number of blocks in each road:
  nbt = 2*nbc + (nmv - 2*nbc*nmg)
  for ird in range(2):
    # Decide range of scan-columns {ix0..ix1} of this road:
    ix0 = ix0_rd0 + ird*ixstep_rd
    ix1 = ix0 + nx_rd
      
    # Blocks in road {ird}:
    bc_prev = None # Previous block in same road.
    iy1 = iy0_rd - 1       # Scanline index of top of previous block.
    for ib in range(nbt):
      # Decide number of raster traces in this block:
      nmvi = nmg if ib < nbc or ib >= nbt - nbc else 1
      
      # Decide scan line span {iy0..iy1} for block {ib}:
      iy0 = iy1 + 1
      iy1 = iy0 + nmvi - 1
      assert iy0 <= iy1 and iy1 < iy0_rd + nmv

      bc_this = make_raster_block(org, ix0,ix1, iy0,iy1, mp_fill, mp_jump)
      BCS.append(bc_this)
      if bc_prev != None:
        # ??? Should use {seam.from_blocks} ???
        oph_prev = block.choice(bc_prev, 0)
        oph_this = block.choice(bc_this, 0)
        ct = contact_example.raster_raster_contact(oph_prev, oph_this)
        CTS.append(ct)
      bc_prev = bc_this
    assert iy1 == iy0_rd + nmv - 1

  # Add island blocks:
  for kx in range(3):
    for ky in range(nis):
      bc_this = make_island_block(kx, ky)
      BCS.append(bc_this)

  return BCS, CTS
  # ----------------------------------------------------------------------
  
def make_raster_block(org, ix0, ix1, iy0, iy1, mp_fill, mp_jump):
  # Returns a block that is a serpentine raster path
  # spanning scan-lines with numbers {iy0} to {iy1} and scan-columns {ix0} to {ix1},
  # assuming scan-lines and scan-columns are spaced {wdf=with(mp_fill)} apart
  # and the indices starts at the point {org}.
  # The block has {iy0=iy1}, the block is a single raster line. See 
  # {block_example.raster_rectangle}.
  sys.stderr.write("make_raster_block(%s,%s, %s,%s)\n" % (ix0,ix1,iy0,iy1))
  wdf = move_parms.width(mp_fill)
  plo = rn.add(org, (ix0*wdf, iy0*wdf))
  nx_rd = ix1 - ix0 + 1
  ny_rd = iy1 - iy0 + 1
  hor = True
  ver = False
  alt = True
  bc = block_example.raster_rectangle(plo, nx_rd, ny_rd, hor, ver, alt, mp_fill, mp_jump)
  return bc

