\begin{code}
{-# OPTIONS -XRecordWildCards -XNamedFieldPuns #-}
-- | This module contains the function to approximate a list of curves with
-- degree 3 Bezier curves, using a least squares method.

module Graphics.Typography.Geometry.Approximation(approximate) where

import qualified Data.Vector.Unboxed as UV
import Graphics.Typography.Geometry.Bezier
import Graphics.Typography.Geometry
import Algebra.Polynomials.Bernstein

import Algebra.Polynomials.Numerical
-- import Debug.Trace
rnd::Interval->Double
rnd (Interval a b)=(a+b)/2


-- | Approximates a list of 'Curves' with a list of degree 3 Bernstein curves.
approximate::[Curve]->[(Bernsteinp Int Double,Bernsteinp Int Double)]
approximate []=[]
approximate l0@(h0:_)= -- traceShow "starting" $
  let approx::Double->Double->[Curve]->[(Bernsteinp Int Double,Bernsteinp Int Double)]
      approx _ _ []=[]
      approx x0 y0 (cc@(Circle {..}):s)= -- traceShow "circle" $
        let theta=abs $ t1-t0 in
        if theta <= pi/2 then
          let x0_=cos $ theta/2
              y0_=sin $ theta/2
              x1_=(4-x0_)/3
              y1_=(1-x0_)*(3-x0_)/(3*y0_)

              c0=cos $! theta/2+t0
              s0=sin $! theta/2+t0


              px0=c0*x0_ - s0*y0_
              py0=s0*x0_ + c0*y0_

              px1=c0*x1_ - s0*y1_
              py1=s0*x1_ + c0*y1_

              px2=c0*x1_ + s0*y1_
              py2=s0*x1_ - c0*y1_

              -- px3=c0*x0_ + s0*y0_
              -- py3=s0*x0_ - c0*y0_

              x1=cx0+(a*px0+b*py0)
              y1=cy0+(c*px0+d*py0)

              (Matrix2 a b c d)=matrix
              x=Bernsteinp 4 $ UV.fromList
                [ x0, -- cx0+(a*px3+b*py3),
                  cx0+(a*px2+b*py2),
                  cx0+(a*px1+b*py1),
                  x1]

              y=Bernsteinp 4 $ UV.fromList
                [ y0, -- cy0+(c*px3+d*py3),
                  cy0+(c*px2+d*py2),
                  cy0+(c*px1+d*py1),
                  y1 ]
          in
           (x,y):(approx x1 y1 s)

        else
          let t1'=(t1+t0)/2 in
          approx x0 y0 $ (cc { t1=t1' }):(cc { t0=t1' }):s
{-
      approx x0 y0 (h@(Bezier{}):s)=
        -- incorrect !
        (restriction (cx h) (t0 h) (t1 h),
         restriction (cy h) (t0 h) (t1 h)):
        (approx (UV.last $ coefs $ cx h)
         (UV.last $ coefs $ cy h) s)
-}
      -- Ce qui suit est une methode de moindres carres
      approx x0 y0 (off_:s)= -- traceShow ("offset") $
        -- On commence par chercher les points ou la derivee de la norme
        -- de la tangente est maximale. C'est la qu'on va couper s'il y
        -- a un probleme.
        let bx=restriction (cx off_) (t0 off_) (t1 off_)
            by=restriction (cy off_) (t0 off_) (t1 off_)
            off=off_ { cx=bx,cy=by,t0=0,t1=1 }
            ibx=elevate (bounds by-bounds bx) $ intervalize bx
            iby=elevate (bounds bx-bounds by) $ intervalize by
            points=
              let np=10 in
              map (\x->(x/np,x/np)) [0..np]
            -- Ensuite, moindres carres standard, comme dans Hoschek 88.

            vx0=ibx?1-ibx?0
            vy0=iby?1-iby?0
            vx1=ibx?(bounds ibx-2)-ibx?(bounds ibx-1)
            vy1=iby?(bounds iby-2)-iby?(bounds iby-1)

            (wx0,wy0)=evalCurve off 0
            (wx1,wy1)=evalCurve off 1

            wx=Bernsteinp 4 $ UV.fromList [wx0,wx0,wx1,wx1] :: Bernsteinp Int Interval
            wy=Bernsteinp 4 $ UV.fromList [wy0,wy0,wy1,wy1] :: Bernsteinp Int Interval

            bern1=Bernsteinp 4 $ UV.fromList [0,1,0,0] :: Bernsteinp Int Interval
            bern2=Bernsteinp 4 $ UV.fromList [0,0,1,0] :: Bernsteinp Int Interval

            sumAll a b c d x1 y1 ((h1,h2):ss)=

              let h=Interval h1 h2
                  (xi,yi)=evalCurve off h

                  b1=eval bern1 h
                  b2=eval bern2 h

                  a'=a + (vx0*vx0+vy0*vy0)*b1*b1
                  b'=b + (vx0*vx1 + vy0*vy1)*b1*b2
                  c'=c + (vx0*vx1 + vy0*vy1)*b1*b2
                  d'=d + (vx1*vx1+vy1*vy1)*b2*b2

                  dx=xi-(eval wx h)
                  dy=yi-(eval wy h)

                  x1'=x1 + (vx0*dx + vy0*dy)*b1
                  y1'=y1 + (vx1*dx + vy1*dy)*b2
              in
               sumAll a' b' c' d' x1' y1' ss

            sumAll a b c d x1 y1 []=(a,b,c,d,x1,y1)

            (ra,rb,rc,rd,rx1,ry1)=sumAll 0 0 0 0 0 0 points

            (Matrix2 a_ b_ c_ d_)=inverse $ Matrix2 ra rb rc rd
            lambda1=a_*rx1+b_*ry1
            lambda2=c_*rx1+d_*ry1

            -- On a la courbe optimale. Il faut chercher ou on va couper, maintenant
            xap=Bernsteinp 4 $ UV.fromList [wx0,
                                            wx0+lambda1*vx0,
                                            wx1+lambda2*vx1,
                                            wx1]
            yap=Bernsteinp 4 $ UV.fromList [wy0,
                                            wy0+lambda1*vy0,
                                            wy1+lambda2*vy1,
                                            wy1]
            (err,arg)=foldl (\m (h1,h2)->
                              let (xi,yi)=evalCurve off (Interval h1 h2)
                                  xj=eval xap (Interval h1 h2)
                                  yj=eval yap (Interval h1 h2)
                              in
                               max m (iup $ abs $ (xi-xj)*(xi-xj)+(yi-yj)*(yi-yj), (h1+h2)/2))
                      (0,0) points
        in
         if err<=0.01 then
           (desintervalize xap,desintervalize yap):(approx (rnd wx1) (rnd wy1) s)
         else
           approx x0 y0 $
            (off { cx=restriction (cx off) 0 arg,
                   cy=restriction (cy off) 0 arg }):
            (off { cx=restriction (cx off) arg 1,
                   cy=restriction (cy off) arg 1 }):s

      (x0h,y0h)=evalCurve h0 $ Interval (t0 h0) (t0 h0)

  in
   approx (rnd x0h) (rnd y0h) l0

desintervalize::(Bernsteinp a Interval)->(Bernsteinp a Double)
desintervalize b=b { coefs=UV.map rnd $ coefs b}

\end{code}