\begin{code}
{-# OPTIONS -XUnboxedTuples -cpp -XRecordWildCards -XNamedFieldPuns -XBangPatterns -XMagicHash -XScopedTypeVariables #-}
-- | This module contains the necessary calls to the other modules of Metafont'
-- to compute the outlines of a given number of pen strokes. The normal way of
-- using it is by calling 'outlines'. One other possible way would be :
--
-- @
-- let curves=cutAll curvesList in
-- remerge $ contour curves $ intersections curves
-- @

module Graphics.Typography.Geometry.Outlines (cutAll, intersections, contour, remerge, outlines) where

import Algebra.Polynomials.Bernstein
import Algebra.Polynomials.Numerical
import Graphics.Typography.Geometry.Bezier
import Graphics.Typography.Geometry
import Data.List (sort)
import qualified Data.Map.Strict as M
import qualified Data.Vector as V

import Control.Parallel

(!)::V.Vector a->Int->a
(!)=(V.!)

-- | Cuts a curve into a list of consecutive non-selfintersecting curves.
cutNoSelf::Curve->[Curve]
cutNoSelf c@(Circle{})=[c]
cutNoSelf bez@(Bezier{..})=
  let ix=intervalize cx
      dx=derivate ix
      solutions=
        sort $ filter (\(s,t)->(ilow $ eval ix (Interval s s))*
                               (iup $ eval ix (Interval t t)) <= 0) $
        solve 1e-10 (V.singleton dx) (t0,t1)
      roots lastU []=
        if lastU>=t1 then
          []
        else
          [bez { t0=lastU }]
      roots lastU (u:s)
        | u<=lastU = roots lastU s -- on ne coupe pas au debut
        | otherwise =
          (bez { t0=lastU, t1=u }):
          (roots u s)
  in
   roots t0 $ map (\(s,t)->(s+t)/2) solutions

cutNoSelf off@(Offset{..})= -- offset
  let thr=1e-2
      ix=intervalize cx
      iy=intervalize cy
      x'=derivate ix
      y'=derivate iy
      (Matrix2 a b c d)=intervalize matrix
      (Matrix2 a_ b_ c_ d_)=inverse $ intervalize matrix

      xx'=(scale a_ x')+(scale b_ y')
      yy'=(scale c_ x')+(scale d_ y')

      xx''=derivate xx'
      yy''=derivate yy'

      evalC (t::Interval)=
        let norm=sqrt $ (eval xx' t)*(eval xx' t)+(eval yy' t)*(eval yy' t)
            derx=(eval yy'' t)/norm -
                 ((eval yy' t)*((eval xx' t)*(eval xx'' t)+
                                (eval yy' t)*(eval yy'' t)))/(norm*norm*norm)
            dery=(eval xx'' t)/norm -
                 ((eval xx' t)*((eval xx' t)*(eval xx'' t)+
                                (eval yy' t)*(eval yy'' t)))/(norm*norm*norm)
        in
         ((eval x' t)+(a*derx-b*dery), (eval y' t)+(c*derx-d*dery))

      zerosx=
        let verif t lastxx
              | t>=t1 = []
              | otherwise =
                let (xx,_)=evalC (Interval t t) in
                if (iup $ xx*lastxx)<=0 then
                  t:verif (t+thr) xx
                else
                  verif (t+thr) xx



        in
         verif t0 $ fst $ evalC (Interval t0 t0)

      roots lastU []=
        if lastU>=t1 then
          []
        else
          [off { t0=lastU }]
      roots lastU (u:s)
        | u<=lastU = roots lastU s -- on ne coupe pas au debut
        | otherwise =
          (off { t0=lastU, t1=u }):
          (roots u s)
  in
   roots t0 zerosx

-- | @'cutAll' curves@ is the array of all the curves, cut such that
-- each part does not intersect itself.
cutAll::[[Curve]]->V.Vector (V.Vector Curve)
cutAll l=V.fromList $ map (\c->V.fromList $ concatMap cutNoSelf c) l


data Topology=Dedans | SurLaLigne | Dehors deriving (Eq, Ord, Show)

minsert::Ord a=>a->b->M.Map a [b]->M.Map a [b]
minsert x y m=M.insertWith (++) x [y] m

munion::Ord a=>M.Map a [b]->M.Map a [b]->M.Map a [b]
munion=M.unionWith (++)


mdeleteFindMin::Ord a=>M.Map a [b]->(Maybe (a,b),M.Map a [b])
mdeleteFindMin m=
  if M.null m then
    (Nothing, m)
  else
    let ((a,b),m')=M.deleteFindMin m in
    case b of
      []->mdeleteFindMin m'
      (h:s)->(Just (a,h), if null s then m' else M.insert a s m')


-- | Computes the intersections between any pair of curves given
-- as input, in parallel in GHC using @+RTS -N@.
intersections::V.Vector (V.Vector Curve)->
               M.Map (Int,Int,Double) [(Int,Int,Double,Double)]
intersections curves=
  let interAll ci cj
        | ci>=V.length curves = M.empty
        | cj>=V.length curves = interAll (ci+1) (ci+1)
        | otherwise =
            -- traceShow (ci,i,cj,j) $
          let next=interAll ci (cj+1)
              inters
                | ci==cj =
                  V.ifoldl'
                  (\s0 i curvei->
                    V.ifoldl'
                    (\s1 j curvej->
                      foldl (\s2 (ti,ti',tj,tj')->
                              minsert (ci,i,ti) (cj,j+i+1,tj,tj') $
                              minsert (cj,j+i+1,tj) (ci,i,ti,ti') $ s2) s1 $
                      inter curvei curvej
                    )
                    s0 $ V.drop (i+1) (curves!cj)
                  ) M.empty $ V.take (V.length (curves!ci)-1) (curves!ci)
                | otherwise =
                  V.ifoldl'
                  (\s0 i curvei->
                    V.ifoldl'
                    (\s1 j curvej->
                      foldl (\s2 (ti,ti',tj,tj')->
                              minsert (ci,i,ti) (cj,j,tj,tj') $
                              minsert (ci,i,ti') (cj,j,tj,tj') $
                              minsert (cj,j,tj) (ci,i,ti,ti') $
                              minsert (cj,j,tj') (ci,i,ti,ti') $ s2) s1 $
                      inter curvei curvej
                    )
                    s0 (curves!cj)
                  ) M.empty $ V.take (V.length (curves!ci)-1) (curves!ci)
          in
           (next`par`inters)`seq`
           (next`munion`inters)
  in
   interAll 0 0

-- | 'contour' takes the curves and the intersections computed as in 'intersections',
-- and outputs a list of all simple closed paths defined by the curves in the input.
contour::V.Vector (V.Vector Curve)->
         M.Map (Int,Int,Double) [(Int,Int,Double,Double)]->
         [[(Int,Int,Double,Double)]]
contour curves inters0=

  let allPaths inters1 passages1=
        let (first,inters2)=mdeleteFindMin inters1 in
        case first of
          Nothing->[]
          Just ((ci0,i0,ti0),(cj0,j0,tj0a,tj0b))->
            --traceShow ("new path",pi0,pj0) $
            let walk ci i tia tib inters passages=
                  --traceShow ("point",ci,i,tia,tib) $ traceShow (inters) $
                  let (a,b)=M.split (ci,i,tib) inters
                      (next,b')=mdeleteFindMin b
                  in
                   case next of
                     Nothing-> -- traceShow ("echec 1") $
                              ([],a,passages)
                     Just ((ci',i',ti'),(cj,j,tja,tjb))
                       | ci==ci0 && i==i0 && (ci',i',ti')>=(ci,i,ti0)->
                         -- fin du chemin
                         ([(ci,i,tia,ti0)],a`munion`b',passages)

                       | ci==ci' && i==i' ->
                         let isVisible=
                               let tt=(tia+ti')/2
                                   (xi,yi)=evalCurve (curves!ci!i) (Interval tt tt)
                               in
                                V.foldl (\vis cur->
                                          vis &&
                                          iup (distance xi yi $ (cur!0) {t0=0,t1=1})>=1)
                                True curves
                         in
                          if (not isVisible) then
                            --traceShow ("invisible",pi') $
                            ([],a`munion`b',passages)
                          else
                           let alreadyPassed=
                                 let (_,p1)=M.split (ci,i,ti') passages in
                                 (not $ M.null p1) &&
                                 (let ((ci_,i_,_),ti'_)=M.findMin p1 in
                                   ci_==ci && i_==i && ti'_<=ti')
                           in
                            if alreadyPassed then
                              --traceShow ("already passed",pi') $
                              ([],a`munion`b',passages)
                            else
                              --traceShow ("trying",pi') $
                              let (nextPath,nextInters,nextPassages)=
                                    walk cj j tja tjb (a`munion`b') $
                                    M.insert (ci,i,ti') tia passages
                              in
                               if null nextPath then
                                 walk ci i tia tib (a`munion`b') passages
                               else
                                 ((ci,i,tia,ti'):nextPath,
                                  nextInters,
                                  M.insert (ci,i,ti') tia nextPassages)
                       | otherwise -> --traceShow ("echec 2",ci',i',ti') $
                           ([],inters,passages)

                (path,inters3,passages1')=walk cj0 j0 tj0a tj0b inters2 passages1
            in
             if null path then
               --traceShow ("abandon") $
               allPaths inters3 passages1'
             else
               --traceShow ("reussi") $
               path:(allPaths inters3 passages1')
  in
   allPaths inters0 M.empty

-- | 'remerge' takes the curves, the output of 'contour', and outputs
-- the list of "remerged" curves, i.e. where the parts free of self-intersections
-- are glued back to each other.
remerge::V.Vector (V.Vector Curve)->[(Int,Int,Double,Double)]->[Curve]
remerge _ []=[]
remerge curves [(ci,i,ti0,ti1)]=[(curves!ci!i) { t0=ti0,t1=ti1 }]
remerge curves (l@((ci,i,ti0,_):s))=

  let (cj,j,_,tj1)=last s in
  if ci==cj && j+1==i && tj1==ti0 then
    -- dans ce cas, le dernier est colle au premier
    let takeFirsts []=(# [],[] #)
        takeFirsts ((h@(ci',_,_,_)):ss)
          | ci'==ci =
            let (# u,v #)=takeFirsts ss in
            (# h:u, v #)
          | otherwise = (# [],h:ss #)
        (# uu,vv #)=takeFirsts l
    in
     remerge_ $ vv++uu
  else
    remerge_ l

  where
    remerge_ []=[]
    remerge_ [(cj,j,tj0,tj1)]=[(curves!cj!j) { t0=tj0,t1=tj1 }]
    remerge_ ((cj,j,tj0,tj1):(cck@(ck,k,tk0,_)):ss)
      | cj==ck && k==j+1 && tj1==tk0 =
        let (h':s')=remerge_ $ cck:ss in
        (h' { t0=tj0 }) : s'

      | otherwise =
          ((curves!cj!j) { t0=tj0,t1=tj1 }) : (remerge_ $ cck:ss)


-- | Takes a list of curves, potentially offset, and outputs the relevants part
-- of the outlines.
outlines::[[Curve]]->[[Curve]]
outlines curves=
  let curves'=cutAll curves in
  map (remerge curves') $ contour curves' $ intersections curves'

\end{code}