# Implementation of module {example_block}.
# Last edited on 2021-02-18 21:11:41 by jstolfi

import example_block
import example_path
import block
import path
import move
import contact
import hacks
import rn
import pyx
import sys
from math import sqrt, sin, cos, acos, floor, ceil, inf, nan, pi

def single_raster(xlo, xhi, y, wd, parms):
  p = (xlo, y)
  q = (xhi, y)
  mv = move.make(p, q, wd, parms)
  ph = path.from_moves((mv,))
  bc = block.from_paths((ph, path.rev(ph),))
  block.validate(bc)
  return bc

def raster_rectangle(xlo, xhi, ylo, wd, nr, parms):
  assert nr > 0
  if nr == 1:
    bc = single_raster(xlo, xhi, ylo, wd, parms)
  else:
    # Make a list {mvs} of {nr} raster traces, all from {xlo} to {xhi}:
    mvs = []
    for k in range(nr):
      yk = ylo + k*wd
      pk = (xlo, yk)
      qk = (xhi, yk)
      mvk = move.make(pk, qk, wd, parms)
      mvs.append(mvk)
    # Now create two serpentine paths using those moves:
    phs = [None,None]
    for dr0 in range(2):
      drk = dr0
      rphsk = [] # List of single-raster paths in alternating directions:
      for k in range(nr):
        mvk = mvs[k]
        omvk = move.orient(mvk,drk)
        rphsk.append(path.from_moves((omvk,)))
        drk = 1 - drk
      # Turn the list of raster paths {ophsk} into a path {phs[dr0]} with links:
      phs[dr0] = path.concat(rphsk, jumps = False, parms = parms)
    # Now create the block:
    bc = block.from_paths((phs[0], path.rev(phs[0]), phs[1], path.rev(phs[1]),))
  block.validate(bc)
  return bc

def onion(ctr, Rc, wdc, Rf, wdf, phase, parms):
  dimp_max = 0.1*wdc  # Max deviation allowed from circle to polygon.
  dang_max = 2*acos(1 - dimp_max/Rc) # Max angle between polygon vertices.
  nt = max(3, 4*ceil(pi/2/dang_max)) # Number of traces in contour.
  ph, Rin, Rot = example_path.onion(ctr, Rc, wdc, Rf, wdf, phase, nt, parms)
  bc = block.from_paths((ph,))
  block.validate(bc)
  return bc

# CONTACTS

def raster_raster_contact(bc0, bc1, parms):
  # 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]
    # Pick some choice of the block, {bc0}  or {bc1}
    ophk= block.choice(bck,0)
    # Find the 
    nk = path.nelems(ophk)
    omvk_lo = path.elem(ophk,0)
    omvk_hi = path.elem(ophk,nk-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
    # 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], parms)
  return ct
