module Diagrams.TwoD.Path
       ( 
         stroke, stroke'
       , strokePath, strokeP, strokePath', strokeP'
       , strokeTrail, strokeT, strokeTrail', strokeT'
       , strokeLine, strokeLoop
       , strokeLocTrail, strokeLocT, strokeLocLine, strokeLocLoop
         
       , FillRule(..)
       , getFillRule, fillRule, _fillRule
       , StrokeOpts(..), vertexNames, queryFillRule
         
       , isInsideWinding, isInsideEvenOdd
         
       , Clip(..), _Clip, _clip
       , clipBy, clipTo, clipped
         
       , intersectPoints, intersectPoints'
       , intersectPointsP, intersectPointsP'
       , intersectPointsT, intersectPointsT'
       ) where
import           Control.Applicative       (liftA2)
import           Control.Lens              hiding (at, transform)
import qualified Data.Foldable             as F
import           Data.Semigroup
import           Data.Typeable
import           Data.Default.Class
import           Diagrams.Angle
import           Diagrams.Combinators      (withEnvelope, withTrace)
import           Diagrams.Core
import           Diagrams.Core.Trace
import           Diagrams.Located          (Located, mapLoc, unLoc)
import           Diagrams.Parametric
import           Diagrams.Path
import           Diagrams.Segment
import           Diagrams.Solve.Polynomial
import           Diagrams.Trail
import           Diagrams.TrailLike
import           Diagrams.TwoD.Segment
import           Diagrams.TwoD.Types
import           Diagrams.TwoD.Vector
import           Diagrams.Util             (tau)
import           Linear.Affine
import           Linear.Vector
instance RealFloat n => Traced (Trail V2 n) where
  getTrace = withLine $
      foldr
        (\seg bds -> moveOriginBy (negated . atEnd $ seg) bds <> getTrace seg)
        mempty
    . lineSegments
instance RealFloat n => Traced (Path V2 n) where
  getTrace = F.foldMap getTrace . op Path
data FillRule = Winding  
                         
                         
              | EvenOdd  
                         
                         
                         
                         
    deriving (Show, Typeable, Eq, Ord)
instance AttributeClass FillRule
instance Semigroup FillRule where
  _ <> b = b
instance Default FillRule where
  def = Winding
data StrokeOpts a
  = StrokeOpts
    { _vertexNames   :: [[a]]
    , _queryFillRule :: FillRule
    }
makeLensesWith (generateSignatures .~ False $ lensRules) ''StrokeOpts
vertexNames :: forall a a'. Lens (StrokeOpts a) (StrokeOpts a') [[a]] [[a']]
queryFillRule :: forall a. Lens' (StrokeOpts a) FillRule
instance Default (StrokeOpts a) where
  def = StrokeOpts
        { _vertexNames    = []
        , _queryFillRule = def
        }
stroke :: (InSpace V2 n t, ToPath t, TypeableFloat n, Renderable (Path V2 n) b)
       => t -> QDiagram b V2 n Any
stroke = strokeP . toPath
stroke' :: (InSpace V2 n t, ToPath t, TypeableFloat n, Renderable (Path V2 n) b, IsName a)
       => StrokeOpts a -> t -> QDiagram b V2 n Any
stroke' opts = strokeP' opts . toPath
strokeP :: (TypeableFloat n, Renderable (Path V2 n) b)
        => Path V2 n -> QDiagram b V2 n Any
strokeP = strokeP' (def :: StrokeOpts ())
strokePath :: (TypeableFloat n, Renderable (Path V2 n) b)
        => Path V2 n -> QDiagram b V2 n Any
strokePath = strokeP
instance (TypeableFloat n, Renderable (Path V2 n) b)
    => TrailLike (QDiagram b V2 n Any) where
  trailLike = strokeP . trailLike
strokeP' :: (TypeableFloat n, Renderable (Path V2 n) b, IsName a)
    => StrokeOpts a -> Path V2 n -> QDiagram b V2 n Any
strokeP' opts path
  | null (pLines ^. _Wrapped') = mkP pLoops
  | null (pLoops ^. _Wrapped') = mkP pLines
  | otherwise                  = mkP pLines <> mkP pLoops
  where
    (pLines,pLoops) = partitionPath (isLine . unLoc) path
    mkP p
      = mkQD (Prim p)
         (getEnvelope p)
         (getTrace p)
         (fromNames . concat $
           zipWith zip (opts^.vertexNames) ((map . map) subPoint (pathVertices p))
         )
         (Query $ Any . flip (runFillRule (opts^.queryFillRule)) p)
strokePath' :: (TypeableFloat n, Renderable (Path V2 n) b, IsName a)
    => StrokeOpts a -> Path V2 n -> QDiagram b V2 n Any
strokePath' = strokeP'
strokeTrail :: (TypeableFloat n, Renderable (Path V2 n) b)
            => Trail V2 n -> QDiagram b V2 n Any
strokeTrail = stroke . pathFromTrail
strokeT :: (TypeableFloat n, Renderable (Path V2 n) b)
        => Trail V2 n -> QDiagram b V2 n Any
strokeT = strokeTrail
strokeTrail' :: (TypeableFloat n, Renderable (Path V2 n) b, IsName a)
             => StrokeOpts a -> Trail V2 n -> QDiagram b V2 n Any
strokeTrail' opts = stroke' opts . pathFromTrail
strokeT' :: (TypeableFloat n, Renderable (Path V2 n) b, IsName a)
         => StrokeOpts a -> Trail V2 n -> QDiagram b V2 n Any
strokeT' = strokeTrail'
strokeLine :: (TypeableFloat n, Renderable (Path V2 n) b)
           => Trail' Line V2 n -> QDiagram b V2 n Any
strokeLine = strokeT . wrapLine
strokeLoop :: (TypeableFloat n, Renderable (Path V2 n) b)
           => Trail' Loop V2 n -> QDiagram b V2 n Any
strokeLoop = strokeT . wrapLoop
strokeLocTrail :: (TypeableFloat n, Renderable (Path V2 n) b)
               => Located (Trail V2 n) -> QDiagram b V2 n Any
strokeLocTrail = strokeP . trailLike
strokeLocT :: (TypeableFloat n, Renderable (Path V2 n) b)
           => Located (Trail V2 n) -> QDiagram b V2 n Any
strokeLocT = strokeLocTrail
strokeLocLine :: (TypeableFloat n, Renderable (Path V2 n) b)
              => Located (Trail' Line V2 n) -> QDiagram b V2 n Any
strokeLocLine = strokeP . trailLike . mapLoc wrapLine
strokeLocLoop :: (TypeableFloat n, Renderable (Path V2 n) b)
              => Located (Trail' Loop V2 n) -> QDiagram b V2 n Any
strokeLocLoop = strokeP . trailLike . mapLoc wrapLoop
runFillRule :: RealFloat n => FillRule -> Point V2 n -> Path V2 n -> Bool
runFillRule Winding = isInsideWinding
runFillRule EvenOdd = isInsideEvenOdd
getFillRule :: FillRule -> FillRule
getFillRule = id
fillRule :: HasStyle a => FillRule -> a -> a
fillRule = applyAttr
_fillRule :: Lens' (Style V2 n) FillRule
_fillRule = atAttr . non def
isInsideWinding :: RealFloat n => Point V2 n -> Path V2 n -> Bool
isInsideWinding p = (/= 0) . crossings p
isInsideEvenOdd :: RealFloat n => Point V2 n -> Path V2 n -> Bool
isInsideEvenOdd p = odd . crossings p
crossings :: RealFloat n => Point V2 n -> Path V2 n -> Int
crossings p = F.sum . map (trailCrossings p) . op Path
trailCrossings :: RealFloat n => Point V2 n -> Located (Trail V2 n) -> Int
  
trailCrossings _ t | not (isLoop (unLoc t)) = 0
trailCrossings p@(unp2 -> (x,y)) tr
  = sum . map test $ fixTrail tr
  where
    test (FLinear a@(unp2 -> (_,ay)) b@(unp2 -> (_,by)))
      | ay <= y && by > y && isLeft a b > 0 =  1
      | by <= y && ay > y && isLeft a b < 0 = 1
      | otherwise                           =  0
    test c@(FCubic (P x1@(V2 _ x1y))
                   (P c1@(V2 _ c1y))
                   (P c2@(V2 _ c2y))
                   (P x2@(V2 _ x2y))
           ) =
        sum . map testT $ ts
      where ts = filter (liftA2 (&&) (>=0) (<=1))
               $ cubForm (  x1y + 3*c1y  3*c2y + x2y)
                         ( 3*x1y  6*c1y + 3*c2y)
                         (3*x1y + 3*c1y)
                         (x1y  y)
            testT t = let (unp2 -> (px,_)) = c `atParam` t
                      in  if px > x then signFromDerivAt t else 0
            signFromDerivAt t =
              let v =  (3*t*t) *^ ((1)*^x1 ^+^ 3*^c1 ^-^ 3*^c2 ^+^ x2)
                   ^+^ (2*t)   *^ (3*^x1 ^-^ 6*^c1 ^+^ 3*^c2)
                   ^+^            ((3)*^x1 ^+^ 3*^c1)
                  ang = v ^. _theta . rad
              in  case () of _ | 0      < ang && ang < tau/2 && t < 1 ->  1
                               | tau/2 < ang && ang < 0     && t > 0 -> 1
                               | otherwise                            ->  0
    isLeft a b = cross2 (b .-. a) (p .-. a)
newtype Clip n = Clip [Path V2 n]
  deriving (Typeable, Semigroup)
makeWrapped ''Clip
instance Typeable n => AttributeClass (Clip n)
instance AsEmpty (Clip n) where
  _Empty = _Clip . _Empty
type instance V (Clip n) = V2
type instance N (Clip n) = n
instance (OrderedField n) => Transformable (Clip n) where
  transform t (Clip ps) = Clip (transform t ps)
_Clip :: Iso (Clip n) (Clip n') [Path V2 n] [Path V2 n']
_Clip = _Wrapped
_clip :: (Typeable n, OrderedField n) => Lens' (Style V2 n) [Path V2 n]
_clip = atTAttr . non' _Empty . _Clip
clipBy :: (HasStyle a, V a ~ V2, N a ~ n, TypeableFloat n) => Path V2 n -> a -> a
clipBy = applyTAttr . Clip . (:[])
clipTo :: (TypeableFloat n, Renderable (Path V2 n) b)
  => Path V2 n -> QDiagram b V2 n Any -> QDiagram b V2 n Any
clipTo p d = setTrace intersectionTrace . toEnvelope $ clipBy p d
  where
    envP = appEnvelope . getEnvelope $ p
    envD = appEnvelope . getEnvelope $ d
    toEnvelope = case (envP, envD) of
      (Just eP, Just eD) -> setEnvelope . mkEnvelope $ \v -> min (eP v) (eD v)
      (_, _)             -> id
    intersectionTrace = Trace traceIntersections
    traceIntersections pt v =
        
        onSortedList (filter pInside) (appTrace (getTrace d) pt v) <>
        
        onSortedList (filter dInside) (appTrace (getTrace p) pt v) where
          newPt dist = pt .+^ v ^* dist
          pInside dDist = runFillRule Winding (newPt dDist) p
          dInside pDist = getAny . sample d $ newPt pDist
clipped :: (TypeableFloat n, Renderable (Path V2 n) b)
  => Path V2 n -> QDiagram b V2 n Any -> QDiagram b V2 n Any
clipped p = withTrace p . withEnvelope p . clipBy p
intersectPoints :: (InSpace V2 n t, SameSpace t s, ToPath t, ToPath s, OrderedField n)
  => t -> s -> [P2 n]
intersectPoints = intersectPoints' 1e-10
intersectPoints' :: (InSpace V2 n t, SameSpace t s, ToPath t, ToPath s, OrderedField n)
  => n -> t -> s -> [P2 n]
intersectPoints' eps t s = intersectPointsP' eps (toPath t) (toPath s)
intersectPointsP :: OrderedField n => Path V2 n -> Path V2 n -> [P2 n]
intersectPointsP = intersectPointsP' 1e-10
intersectPointsP' :: OrderedField n => n -> Path V2 n -> Path V2 n -> [P2 n]
intersectPointsP' eps as bs = do
  a <- pathTrails as
  b <- pathTrails bs
  intersectPointsT' eps a b
intersectPointsT :: OrderedField n => Located (Trail V2 n) -> Located (Trail V2 n) -> [P2 n]
intersectPointsT = intersectPointsT' 1e-10
intersectPointsT' :: OrderedField n => n -> Located (Trail V2 n) -> Located (Trail V2 n) -> [P2 n]
intersectPointsT' eps as bs = do
  a <- fixTrail as
  b <- fixTrail bs
  intersectPointsS' eps a b