from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import sys
from math import sin
from math import cos
from math import hypot

import fromcubic
import tocubic

import cornu


def eps_prologue(x0, y0, x1, y1, draw_box=False):
    print('%!PS-Adobe-3.0 EPSF')
    print('%%BoundingBox:', x0, y0, x1, y1)
    print('%%EndComments')
    print('%%EndProlog')
    print('%%Page: 1 1')
    if draw_box:
        print(x0, y0, 'moveto', x0, y1, 'lineto', x1, y1, 'lineto', x1, y0, 'lineto closepath stroke')


def eps_trailer():
    print('%%EOF')


def fit_cubic_superfast(z0, z1, arclen, th0, th1, aab):
    chord = hypot(z1[0] - z0[0], z1[1] - z0[1])
    cth0, sth0 = cos(th0), sin(th0)
    cth1, sth1 = -cos(th1), -sin(th1)
    armlen = .66667 * arclen
    a = armlen * aab
    b = armlen - a
    bz = [z0, (z0[0] + cth0 * a, z0[1] + sth0 * a),
          (z1[0] + cth1 * b, z1[1] + sth1 * b), z1]
    return bz


def fit_cubic(z0, z1, arclen, th_fn, fast, aabmin=0, aabmax=1.):
    chord = hypot(z1[0] - z0[0], z1[1] - z0[1])
    if arclen < 1.000001 * chord:
        return [z0, z1], 0
    th0 = th_fn(0)
    th1 = th_fn(arclen)
    imax = 4
    jmax = 10
    if fast:
        imax = 1
        jmax = 0
    for i in range(imax):
        for j in range(jmax + 1):
            if jmax == 0:
                aab = 0.5 * (aabmin + aabmax)
            else:
                aab = aabmin + (aabmax - aabmin) * j / jmax
            if fast == 2:
                bz = fit_cubic_superfast(z0, z1, arclen, th0, th1, aab)
            else:
                bz = tocubic.fit_cubic_arclen(z0, z1, arclen, th0, th1, aab)
            score = tocubic.measure_bz_rk4(bz, arclen, th_fn)
            print('% aab =', aab, 'score =', score)
            sys.stdout.flush()
            if j == 0 or score < best_score:
                best_score = score
                best_aab = aab
                best_bz = bz
        daab = .06 * (aabmax - aabmin)
        aabmin = max(0, best_aab - daab)
        aabmax = min(1, best_aab + daab)
        print('%--- best_aab =', best_aab)
    return best_bz, best_score


def cornu_to_cubic(t0, t1, figno):
    if figno == 1:
        aabmin = 0
        aabmax = 0.4
    elif figno == 2:
        aabmin = 0.5
        aabmax = 1.
    else:
        aabmin = 0
        aabmax = 1.
    fast = 0
    if figno == 3:
        fast = 1
    elif figno == 4:
        fast = 2

    def th_fn(s):
        return (s + t0) ** 2

    y0, x0 = cornu.eval_cornu(t0)
    y1, x1 = cornu.eval_cornu(t1)
    bz, score = fit_cubic((x0, y0), (x1, y1), t1 - t0, th_fn, fast, aabmin, aabmax)
    return bz, score


def plot_k_of_bz(bz):
    dbz = tocubic.bz_deriv(bz)
    ddbz = tocubic.bz_deriv(dbz)
    cmd = 'moveto'
    ss = [0]

    def arclength_deriv(x, ss):
        dx, dy = tocubic.bz_eval(dbz, x)
        return [hypot(dx, dy)]

    dt = 0.01
    t = 0
    for i in range(101):
        dx, dy = tocubic.bz_eval(dbz, t)
        ddx, ddy = tocubic.bz_eval(ddbz, t)
        k = (ddy * dx - dy * ddx) / (dx * dx + dy * dy) ** 1.5
        print(100 + 500 * ss[0], 100 + 200 * k, cmd)
        cmd = 'lineto'

        dsdx = arclength_deriv(t, ss)
        tocubic.rk4(ss, dsdx, t, .01, arclength_deriv)
        t += dt
    print('stroke')


def plot_k_nominal(s0, s1):
    k0 = 2 * s0
    k1 = 2 * s1
    print('gsave 0.5 setlinewidth')
    print(100, 100 + 200 * k0, 'moveto')
    print(100 + 500 * (s1 - s0), 100 + 200 * k1, 'lineto')
    print('stroke grestore')


def simple_bez():
    eps_prologue(95, 126, 552, 508, False)
    tocubic.plot_prolog()
    print('/ss 1.5 def')
    print('/circle { ss 0 moveto currentpoint exch ss sub exch ss 0 360 arc } bind def')
    bz, score = cornu_to_cubic(.5, 1.1, 2)
    fromcubic.plot_bzs([[bz]], (-400, 100), 1000, True)
    print('stroke')
    print('/Times-Roman 12 selectfont')
    print('95 130 moveto ((x0, y0)) show')
    print('360 200 moveto ((x1, y1)) show')
    print('480 340 moveto ((x2, y2)) show')
    print('505 495 moveto ((x3, y3)) show')
    print('showpage')
    eps_trailer()


def fast_bez(figno):
    if figno == 3:
        y1 = 520
    else:
        y1 = 550
    eps_prologue(95, 140, 552, y1, False)
    tocubic.plot_prolog()
    print('/ss 1.5 def')
    print('/circle { ss 0 moveto currentpoint exch ss sub exch ss 0 360 arc } bind def')
    bz, score = cornu_to_cubic(.5, 1.1, figno)
    fromcubic.plot_bzs([[bz]], (-400, 100), 1000, True)
    print('stroke')
    plot_k_nominal(.5, 1.1)
    plot_k_of_bz(bz)
    print('showpage')
    eps_trailer()


def bezfig(s1):
    eps_prologue(95, 38, 510, 550, False)
    # print('0.5 0.5 scale 500 100 translate')
    tocubic.plot_prolog()
    print('/ss 1.5 def')
    print('/circle { ss 0 moveto currentpoint exch ss sub exch ss 0 360 arc } bind def')
    bz, score = cornu_to_cubic(.5, 0.85, 1)
    fromcubic.plot_bzs([[bz]], (-400, 0), 1000, True)
    print('stroke')
    plot_k_nominal(.5, 0.85)
    plot_k_of_bz(bz)
    bz, score = cornu_to_cubic(.5, 0.85, 2)
    fromcubic.plot_bzs([[bz]], (-400, 100), 1000, True)
    print('stroke')
    print('gsave 0 50 translate')
    plot_k_nominal(.5, .85)
    plot_k_of_bz(bz)
    print('grestore')
    print('showpage')


if __name__ == '__main__':
    figno = int(sys.argv[1])
    if figno == 0:
        simple_bez()
    elif figno == 1:
        bezfig(1.0)
    elif figno == 2:
        bezfig(0.85)
    else:
        fast_bez(figno)
    # fast_bez(4)
