# Implementation of module {hacks}.
# Last edited on 2021-03-16 12:09:06 by jstolfi

import color
import palette
import rn
import pyx
from math import sqrt, floor, ceil, sin, cos, inf, nan, pi
from PIL import Image 
import sys

def real_quadratic_roots(A, B, C):
  assert A != 0
  if A < 0:
    # Reverse signs to make {A} positive:
    A = -A; B = -B; C = -C  
  # Rescale by replacing {t = M s} so that {A} is 1:
  M = 1/sqrt(A)
  A = 1; B = B*M
  # sys.stderr.write("A = %10.7f B = %10.7f C = %10.7f M = %12.7f \n" % (A, B, C, M))
  # Now solve {s^2 + B*s + C = 0} 
  Delta = B*B - 4*C
  if Delta <= 0:
    # Zero or one root
    return None, None
  elif Delta == inf:
    # Pretend the roots are infinite but symmetric:
    return -inf, +inf
  else:
    sD = sqrt(Delta)
    s0 = (-B - sD)/2; t0 = M*s0
    s1 = (-B + sD)/2; t1 = M*s1
    assert -inf < t0 and t0 < t1 and t1 < +inf
    return t0, t1

# GEOMETRY

def is_point(p):
  if not (type(p) is tuple or type(p) is list): return False
  if len(p) != 2: return False
  for c in p:
    if (type(c) is not float) and (type(c) is not int): return False
  return True

# PLOTTING

def round_box(B, mrg):
  plo = ( floor(B[0][0] - mrg), floor(B[0][1] - mrg)  )
  phi = ( ceil(B[1][0] + mrg), ceil(B[1][1] + mrg)  )
  return (plo, phi)
  # ----------------------------------------------------------------------

def plot_line(c, clr, wd, p, q):
  if clr == None: clr = pyx.color.rgb.black
  peps = 0 if p != q else 1.0e-8*max(1,rn.norm(p))
  sty = [
      pyx.style.linejoin.round,
      pyx.style.linecap.round,
      pyx.style.linewidth(wd), 
      clr
    ]
  c.stroke(pyx.path.line(p[0]-peps, p[1]-peps, q[0]+peps, q[1]+peps), sty)

def plot_box(c, clr, dp, B):
  if clr == None: clr = pyx.color.rgb.black
  if dp == None: dp = (0,0)
  xp = B[0][0] + dp[0]; xsz = B[1][0] - B[0][0]
  yp = B[0][1] + dp[1]; ysz = B[1][1] - B[0][1]
  sty = [ clr ]
  c.fill(pyx.path.rect(xp, yp, xsz, ysz), sty)

def plot_frame(c, clr, wd, dp, B, mrg):
  if clr == None: clr = pyx.color.rgb.black
  if dp == None: dp = (0,0)
  if mrg == None: mrg = 0
  xmin = dp[0] + B[0][0] + mrg; xmax = dp[0] + B[1][0] - mrg
  ymin = dp[1] + B[0][1] + mrg; ymax = dp[1] + B[1][1] - mrg
  
  sty = [
      pyx.style.linejoin.round,
      pyx.style.linecap.round,
      pyx.style.linewidth(wd), 
      clr
    ]
  c.stroke(pyx.path.line( xmin, ymin, xmax, ymin), sty)
  c.stroke(pyx.path.line( xmax, ymin, xmax, ymax), sty)
  c.stroke(pyx.path.line( xmax, ymax, xmin, ymax), sty)
  c.stroke(pyx.path.line( xmin, ymax, xmin, ymin), sty)

def plot_grid(c, clr, wd, dp, B, mrg, xstep, ystep):
  if clr == None: clr = pyx.color.grey(0.8)
  if dp == None: dp = (0,0)
  if mrg == None: mrg = 0
  if xstep == None: xstep = 1
  if ystep == None: ystep = 1

  xmin = B[0][0] + mrg; xmax = B[1][0] - mrg
  ymin = B[0][1] + mrg; ymax = B[1][1] - mrg
 
  # Grid line index ranges, with some extra:
  kxmin = int(floor(xmin/xstep)) - 1; kxmax = int(ceil(xmax/xstep)) + 1
  kymin = int(floor(ymin/ystep)) - 1; kymax = int(ceil(ymax/ystep)) + 1
  
  sty = [
      pyx.style.linestyle.dashed, 
      pyx.style.linejoin.round,
      pyx.style.linecap.round,
      pyx.style.linewidth(wd), 
      clr
    ]
  eps = 1.0e-6*wd
  
  for dkx in range(kxmax-kxmin+1):
    x = (kxmin+dkx)*xstep
    if x >= xmin+eps and x < xmax-eps:
      c.stroke(pyx.path.line(dp[0] + x, dp[1] + ymin, dp[0] + x, dp[1] + ymax), sty)
  for dky in range(kymax-kymin+1):
    y = (kymin+dky)*ystep
    if y >= ymin+eps and y < ymax-eps:
      c.stroke(pyx.path.line(dp[0] + xmin, dp[1] + y, dp[0] + xmax, dp[1] + y), sty)

def write_plot(c, name):
  #sys.stderr.write("writing %s.eps, %s.png, %s.eps ...\n" % (name, name, name))
  c.writeEPSfile(name + ".eps")
  convert_eps_to_png(name)
  #convert_eps_to_jpg(name)
  #sys.stderr.write("done.\n")

def convert_eps_to_png(name):
   im = Image.open(name + '.eps')
   im.load(scale=2)
   fig = im.convert('RGBA')
   fig.save(name + '.png', lossless = True)
   im.close()

def convert_eps_to_jpg(name):
   im = Image.open(name + '.eps')
   im.load(scale=2)
   fig = im.convert('RGB')
   fig.save(name + '.jpg', quality = 95)
   im.close()

def adjust_dash_pattern(rdist, rlen, rgap):
  assert rlen > 0
  if rgap <= 0: return False, None # No gaps -- might as wel use solid.
  if rdist <= rlen: return False, None # Not enough space for a single dash.
  
  # Compute the approx number {ngap} of gaps:
  xgap = (rdist - rlen)/(rgap + rlen) # Fractional number of gaps with ideal pattern.
  assert xgap >= 0
  ngap = floor(xgap + 0.5)
  if ngap == 0: ngap = 1
  
  # Check whether {ngap+1} or {ngap-1} may be better:
  nbest = None
  ebest = +inf
  for dn in 0, -1, +1:
    nk = ngap + dn
    if nk >= 0:
      rdk = nk*(rlen+rgap) + rlen
      edk = rdk/rdist if rdk >= rdist else rdist/rdk
      if edk < ebest: ebest = edk; nbest = nk
  assert nbest != None
  
  # Compute the adjustment factor {fac}:
  fac = rdist/(nbest*(rlen+rgap) + rlen)
  minfac = 0.75; maxfac = 1/minfac
  if fac < minfac or fac > maxfac:
    # Would have to shrink or squeeze too much. Only if {rdist} is small, so:
    return False, None
    
  # Okeey:
  dashpat = [ fac*rlen, fac*rgap ]
  return True, dashpat

def trace_colors(nc):
  Ymin = 0.540; Ymax = 0.560
  Smin = 0.700; Smax = 1.000
  colors = [None]*nc
  if nc == 1:
    colors[0] = pyx.color.rgb(0.200, 0.600, 0.000)
  else:
    # Fill half the table with palette colors, half with complements:
    nh = nc//2
    for k in range(nh):
      RGBk = palette.discrete(k, Ymin,Ymax,Smin,Smax)
      colors[k] = pyx.color.rgb(RGBk[0], RGBk[1], RGBk[2])
      colors[nc-1-k] = pyx.color.rgb(1-RGBk[0], 1-RGBk[1], 1- RGBk[2])
    if nc % 2 == 1:
      # Insert a gray color in the middle entry:
      colors[nh] = pyx.color.rgb(0.500,0.500,0.500)
  return colors
  

