# Implementation of module {input_data}
# Last edited on 2021-10-02 16:31:39 by stolfi

import input_data
import block
import path
import path_hp
import move
import move_parms
import contact
import path_example
import block_example
import job_parms

import txt_read
import plot_data
import raster
import raster_regroup
import raster_example

import hacks
import rn

from shapely.geometry import Point
from shapely.geometry.polygon import Polygon
import pyx

import sys
import os
from math import sqrt, sin, cos, floor, ceil, inf, nan, pi

def read_txt(fname, mp_cont, mp_fill, angle):

  rd = open(fname, "r")
  OCRS, OPHS, CTS, Z = txt_read.read(rd, mp_cont, mp_fill, angle)
  rd.close()
  
  return OCRS, OPHS, CTS, Z
  # ----------------------------------------------------------------------

def make_synthetic(dsname, variant, mp_cont, mp_fill):
  if dsname == "patch_array":
    if variant == "2x1x27is":
      islands = True
      cols = 2*4 + 1
      rows = 1
      nx = 3
      ny = 27
    elif variant == "3x1x27no":
      islands = False
      cols = 3*3 + 1
      rows = 1
      nx = 3
      ny = 27
    else:
      assert False, "unknown {variant}"
    mp_link = mp_fill
    OCRS, OPHS, CTS = raster_example.patch_array(cols, rows, nx, ny, islands, mp_cont,mp_fill,mp_link)
    xdir = (1,0); ydir = (0,1) 
    # raster.create_all_raster_raster_contacts(OPHS, xdir, ydir)
    # raster.create_all_raster_raster_links(OPHS, xdir, ydir, mp_link)
  else:
    assert False, "unknown {dsname}"
  Z = 0
  return OCRS, OPHS, CTS, Z
  # ----------------------------------------------------------------------

def create_blocks(OPHS, mp_jump, xdir, ydir, OCRS, initial, split, max_lines, alter):

  debug = False

  if max_lines != None: max_lines = 0
  
  # Define initial grouping:
  if initial == 'original':
    # Keep grouping as in input:
    pass
  elif initial == 'single':
    # Put all elements in one group.
    raster_regroup.merge_all(OPHS)
  elif initial == 'contour':
    # Make one group from each geometrical component of the slice:
    raster_regroup.by_contours(OPHS, OCRS)
  elif initial == 'graph':
    # Make one group from each geometrical component of the slice:
    raster_regroup.by_contacts(OPHS)
  else:
    assert False, "invalid {initial} option"

  if split: raster_regroup.split_at_forks(OPHS)
  if max_lines != 0: raster_regroup.split_by_size(OPHS, max_lines)

  # Separate fillers by group, preserving their order:
  GRS_old, ngr_old = path_hp.separate_paths_by_group(OPHS)

  BCS = []
  CTS = []

  for OPHS_gr in GRS_old:
    if OPHS_gr != None and len(OPHS_gr) > 0:
      # Obtain the two lists of fillers and links, and the number of scanlines:
      BPHS, nsc = define_block_choices_in_group(OPHS_gr, ydir, alter)
      assert nsc >  0
      #  Create the two main paths:
      if nsc == 1:
        # Block is a single scanline.  Only two choices:
        use_links = True
        bph = [ path.concat(BPHS[0], use_links, mp_jump), ]
        bc = block.from_paths((bph[0], path.rev(bph[0]),))
      else:
        # Block spans two or more scanlines.  Four choices:
        bph = [ path.concat(BPHS[i], use_links, mp_jump) for i in range(2) ]
        bc = block.from_paths((bph[0], path.rev(bph[0]), bph[1], path.rev(bph[1]),))
      # Set the path-to-block pointers and the inter-block links:
      for i in range(len(bph)):
        if debug:
          sys.stderr.write("choice bph[%d]:\n" % i)
          path.show(sys.stderr, bph[i], True, 2, 0,0)
          sys.stderr.write("\n")

        path_hp.set_block(bph[i], bc)
        OLKS0 = path.get_links(BPHS[i][0])
        path.set_links(bph[i], OLKS0)
        if debug:
          sys.stderr.write("links in:\n")
          path.show_list(sys.stderr, OLKS0, True,  2)

        OLKS1 = path.get_links(path.rev(BPHS[i][-1]))
        path.set_links(path.rev(bph[i]), OLKS1)
        if debug:
          sys.stderr.write("links out (reversed):\n")
          path.show_list(sys.stderr, OLKS1, True,  2)
      BCS.append(bc)
  return BCS
  # ----------------------------------------------------------------------
      
def define_block_choices_in_group(OPHS_gr, ydir, alter):
  # Same as {create_blocks_from_groups}, except that
  #
  #   (1) Assumes that {OPHS_gr} contains the fillers
  #   of a single block, in scanline order
  #
  #   (2) Returns a pair {OPHS} of lists of fillers and links that 
  #   should be concatenated to produce the two main choices of the
  #   block, as well as the number of scanlines found.

  nsc = 0  # Number of scanlines found so far in group.

  # We keep two lists of fillers and links from the complete scanlines found so far,
  # {BPHS[0]} that begins with the first scanline in left-to-right sense, and
  # {BPHS[1]} that begins with the first scanline in right-to-left sense.
  # Also {SPHS} which is list of fillers and links in the current (maybe incomplete) scanline.
  BPHS = None  # Fillers and links in previous complete scanlines.
  SPHS = []    # Fillers an links in current scanline.

  oph_prev = None # Last filler processed
  y_prev = -inf   # {Y} coord of filler {oph_prev}.
  wd_prev = None  # Width of trace of {oph_prev}
  # Scan elements of old group:
  for oph_this in OPHS_gr + [ None ]:
    # Get {Y} coordinate 
    if oph_this == None:
      y_this = +inf
      wd_this = None
    else:
      x_this, y_this = path.mean_projections(oph_this, None, ydir)
      wd_this = move.width(path.elem(oph_this, 0))
    # Still the same scanline?
    if wd_prev == None or wd_this == None:
      sep = 0
    else:
      sep = (wd_prev + wd_this)/2 # Expected separation between scanlines.
    if y_this - y_prev < 0.1*sep:
      # Looks like they are on the same scanline:
      assert oph_prev != None
      if len(SPHS) > 0:
        # There may be links between paths of the same scanline;
        olk = path.get_connecting_link(oph_prev, oph_this)
        if olk != None: SPHS.append(olk)
      SPHS.append(oph_this)
    else:
      # Looks like we got to a new scanline.
      assert y_this - y_prev > 0.9*sep, "inconsistent scanline spacing"
      nps = len(SPHS) # Number of elements in scanline just completed
      if nps > 0:
        # Scanline is not empty, add to block choices.
        # Reverse order and direction of scanline elements:
        SPHS_rev = [ path.rev(SPHS[nps - 1 -i]) for i in range(nps) ]
        if alter and (nsc % 2 == 0): 
          # Swap {SPHS} and {SPHS_rev}:
          SPHS, SPHS_rev = SPHS_rev, SPHS
        if BPHS == None:
          # First scanline in group:
          BPHS = [ SPHS, SPHS_rev ]
        else:
          # Concatenate current scanline to previous paths:
          for i in range(2):
            SPHSi = (SPHS,SPHS_rev)[i]
            if len(BPHS[i]) > 0:
              olki = path.get_connecting_link(BPHS[i][-1], SPHSi[0])
              if olki != None: BPHS[i].append(olki)
            BPHS[i] += SPHSi
      if oph_this != None:
        nsc += 1 # One more scanline found:
        SPHS = [ oph_this ] # Reset filler list of current scanline.
      else:
        SPHS = None # Just in case.
    oph_prev = oph_this
    y_prev = y_this
    wd_prev = wd_this
      
  return BPHS, nsc
  # ----------------------------------------------------------------------

def describe(wr, o, BCS, SMS, maxcalls, mp_jump):
  wr.write("contact x block table:\n")
  wr.write("\n")
  block.print_contact_table(sys.stderr, BCS, SMS, None)
  wr.write("\n")

  wr.write("number of blocks = %d\n" % len(BCS))
  wr.write("average choices per block = %.3f\n" % blocks.avg_choices(BCS))
  wr.write("lower bound to execution time = %.3f s\n" % block.min_tot_fabtime(BCS))
  wr.write("number of seams = %d\n" % len(SMS))
  min_max_rc = seam.min_max_rcool_list(SMS,mp_jump)
  wr.write("lower bound to max cooling time ratio = %.3f s\n" % min_max_rc)
  if maxcalls == None:
    wr.write("no limit on number of calls\n")
  else:
    wr.write("max allowed calls = %d\n" % maxcalls)
  return
  # ----------------------------------------------------------------------

def describe_elis(wr, o, BCS, CTS, OCRS, number_lines, Delta, maxcalls, parms):
  
  wr.write('------- PARAMETERS:\n')
  job_parms.write(wr, None, parms, None)
  wr.write("----------------------------------------------------------------------\n")

  wr.write('------- DETAILS:\n')
  wr.write("number of contours = %d\n" % len(OCRS))
  wr.write("number of blocks = %d\n" % len(BCS))
  wr.write("number of lines = %d\n" % number_lines)
  wr.write("number of contacts = %d\n" % len(CTS))
  wr.write("average choices per block = %.3f\n" % block.avg_choices(BCS))
  wr.write("lower bound to execution time = %.3f s\n" % block.min_tot_fabtime(BCS))
  wr.write("number of contacts = %d\n" % len(CTS))
  wr.write("lower bound to max cooling time ratio = %.3f s\n" % contact.min_max_rcool(CTS, BCS, mp_jump))

  if Delta == +inf:
    wr.write("no limit on cooling time\n")
  else:
    wr.write("max allowed cooling time = %.3f s\n" % Delta)

  if maxcalls == None:
    wr.write("no limit on number of calls\n")
  else:
    wr.write("max allowed calls = %d\n" % maxcalls)
  
  wr.write("----------------------------------------------------------------------\n")
  wr.write('------- CONTACT x BLOCK TABLE:\n')
  wr.write("\n")
  print_contact_table(wr, BCS, CTS, None)
  wr.write("\n")
  wr.write("----------------------------------------------------------------------\n")

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

def plot_A(fname, BCS, CTS, o, wd_ref):
  
  ncolors = 17
  colors = hacks.trace_colors(ncolors) # Colors to use for different layers.

  # Find the max number of choices in each block:
  nchmax = max(block.nchoices(bc) for bc in BCS)

  # Find the bounding box of all blocks and the starting point:
  B = rn.box_from_point(o)
  B = rn.box_join(B, block.bbox(BCS))
  
  # Plot one choice from each block:
  wd_axes = 0.05*wd_ref
  wd_ct = 0.15*wd_ref; clr_ct = pyx.color.rgb( 0.900, 0.100, 0.000 ) # Contacts.
  wd_start = 5*wd_axes; clr_start = pyx.color.rgb( 0.800, 0.200, 0.000 ) # Initial nozzle position.
  matter = True
 
  for ich in range(nchmax):
    ic = ich % ncolors
    ctraces = colors[ic]
    c, szx,szy = hacks.make_canvas(B, None, True, True, 1, 1)

    axes = True
    dots = True
    arrows = True
    block.plot_choice_list(c, BCS, ich, ctraces, rwd, wd_axes, axes, dots, arrows, matter)
    contact.plot(c, CTS, clr_ct, wd_ct)
    hacks.plot_line(c, clr_start, wd_start, None, o, o)
    hacks.write_plot(c, "%s_ip%02d" % (fname,ich))
  return
  # ----------------------------------------------------------------------

def plot_B(fname, CRS, BCS, CTS, mp_cont, org):
  
  B = block.bbox(BCS)
  if len(CRS) > 0: B = rn.box_join(B, path.bbox(CRS)) 
  if len(CTS) > 0: B = rn.box_join(B, contact.bbox(CTS))
  if org != None:  B = rn.box_include_point(B, org)
  
  # Determine the max number of choices in all blocks:
  nch = 1;
  if BCS != None:
    for bc in BCS:
      nch = max(nch, block.nchoices(bc))

  for ip in range(nch):
    c, szx,szy = hacks.make_canvas(B, None, True, True, 1, 1)

    wd_contact = 0.25*move_parms.width(mp_cont)         # Width of contact lines.
    wd_org = 0.50*move_parms.width(mp_cont)             # Diameter of starting point.
    
    CLRS_blocks = hacks.trace_colors(len(BCS))          # Color of each block.
    clr_contour = pyx.color.rgb( 0.000, 0.000, 0.000 )  # Color of contours.
    clr_contact = pyx.color.rgb( 1.000, 0.000, 0.000 )  # Color of contacts.
    clr_link = pyx.color.rgb( 0.000, 1.000, 1.000 )     # Color of links between blocks.
    clr_org = pyx.color.rgb( 0.000, 0.000, 0.000 )      # Color of initial nozzle position.

    if CRS != None: 
      plot_contours(c, clr_contour, CRS, mp_cont, None)
    
    if BCS != None: 
      plot_links(c, clr_link, BCS, ip)
      plot_blocks(c, CLRS_blocks, BCS, ip)

    if CTS != None: 
      plot_contacts(c, CTS, clr_contact, clr_contact, clr_contact, wd_contact)

    if org != None: 
      hacks.plot_line(c, clr_org, wd_org, None, org, org)

    hacks.write_plot(c, "%s_ip%02d" % (fname, ip))

  return B
  # ----------------------------------------------------------------------

def plot_C(fname, BCS, CTS, o, wd_ref):
  
  nbc = len(BCS)

  # Find the max number of choices in each block:
  nchmax = max(block.nchoices(bc) for bc in BCS)

  # Find the bounding box of all blocks and the starting point:
  B = rn.box_from_point(o)
  B = rn.box_join(B, block.bbox(BCS))

  CLRS = hacks.trace_colors(nbc) # Block colors.
  wd_axes = 0.05*wd_ref
  matter = True

  wd_start = 5*wd_axes
  clr_start = pyx.color.rgb( 0.800, 0.200, 0.000 ) # Initial nozzle position.

  wd_ct = 0.15*wd_ref
  clr_ct = pyx.color.rgb( 0.900, 0.100, 0.000 ) # Contacts.
 
  dp = None
  
  for ich in range(nchmax):
    # Plot choice {ich} from each block:
    c, szx,szy = hacks.make_canvas(B, None, 1, 1)

    axes = True
    dots = True
    arrows = True
    block.plot_choice_list(c, BCS, ich, CLRS, rwd, wd_axes, axes, dots, arrows, matter)
    # Plot contacts relevant to these choices:
    for ct in CTS:
      if contact.is_relevant(ct, BCS, ich):
        arrows_ct = contact.is_directed(ct)
        contact.plot_single(c, ct, dp, clr = clr_ct, wd = wd_ct, sz_tic = 0, arrow = arrow_ct)
    
    # Plot the initial jump:
    hacks.plot_line(c, clr_start, wd_start, None, o, o)
    hacks.write_plot(c, "%s_ip%02d" % (fname, ich))
  return
  # ----------------------------------------------------------------------

def plot_contours(c, clr, CRS, mp_cont, org):
  for cr in CRS:
    if org != None:
      iorg = path.find_nearest_point(cr, org)
      cr = path.shift_contour(cr, iorg)
      org  = path.pfin(cr)
    assert isinstance(cr, path.Path)
    path.plot_single(c, cr, None, False, clr)
  return

def plot_links(c, clr, BCS, ip):
  # Plots the links that connect to the endpoints of choice {ip}
  # of each block. Many links will be plotted twice, but it seems
  # hard to avoid that and it is invisible anyway.
  for ibc in range(len(BCS)):
    bc = BCS[ibc]
    if ip < block.nchoices(bc):
      ophi = block.choice(bc, ip)
      for oph in ophi, path.rev(ophi):
        OLKS = path.get_links(oph)
        for olk in OLKS:
          path.plot_single(c, olk, None, False, clr)
  return

def plot_blocks(c, CLRS, BCS, ip):
  for ibc in range(len(BCS)):
    bc = BCS[ibc]
    if ip < block.nchoices(bc):
      oph = block.choice(bc, ip)
      path.plot_single(c, oph, None, False, CLRS[ibc])
  return

def plot_contacts(c, CTS, clr_C, clr_A, clr_I, wd_contact):
  # Plots contacts in {CTS}, with color {clr_C} if closed, with {clr_A} if active,
  # or with {clr_I} if inactive.

  for ct in CTS:
    bc0 = contact_hp.side_block(ct, 0)
    bc1 = contact_hp.side_block(ct, 1)
    
    done0 = None if bc0 == None else block_hp.used_choice(bc0)
    done1 = None if bc1 == None else block_hp.used_choice(bc1)
    
    if done0 != None and done1 != None:
      # Contact {ct} is closed by {Q}:
      clr = clr_C
    if (done0 == None) != (done1 == None):
      # Contact is active (exctly one side covered by {Q}):
      clr = clr_A
    elif done0 == None and done1 == None:
      # Contact {ct} is still inactive (neither side closed by {Q}):
      clr = clr_I

    contact.plot_single(c, ct, None, clr, wd = wd_contact, sz_tic = 0, arrow = False)

  return

def plot_solution(fname, Q, B): 

  c, szx,szy = hacks.make_canvas(B, None, True, True, 1, 1)
  
  moves_list = path.plot_single(c, Q, None, True, None)
  
  wpoint = 1.0
  clr_org = pyx.color.rgb( 1.000, 0.000, 0.000 ) # Initial nozzle position.
  org = path.pini(Q)
  hacks.plot_line(c, clr_org, wpoint, None, org, org)

  clr_org = pyx.color.rgb( 0.000, 0.000, 1.000 ) # Final nozzle position.

  dst = path.pfin(Q)
  
  hacks.plot_line(c, clr_org, wpoint, None, dst, dst)

  hacks.write_plot(c, fname)

  return

def plot_debug(CRS, BCS, CTS, Q, tag, ncalls, B):

  fname = tag + '_' + ("%07d" % ncalls)
  
  c, szx,szy = hacks.make_canvas(B, None, True, True, 1, 1)

  cpath = [ pyx.color.rgb( 0.000, 0.000, 0.000 ) ]*100
  path.plot_single(c, Q, None, False, cpath)

  if CRS != None: 
    plot_contours(c, clr_contour, CRS, mp_cont, None)
  
  if BCS != None: 
    plot_blocks(c, CLRS_blocks, BCS, 0)

  if CTS != None: 
    plot_contacts(c, CTS, clr_contact, clr_contact, clr_contact, wd_contact)

  hacks.write_plot(c, fname)
  return

