module Algorithms.Geometry.ConvexHull.JarvisMarch(
    convexHull
  , upperHull, upperHull'
  , lowerHull, lowerHull'
  , steepestCcwFrom, steepestCwFrom
  ) where
import           Control.Lens ((^.))
import           Data.Bifunctor
import           Data.Either (either)
import           Data.Ext
import           Data.Foldable
import           Data.Geometry.Point
import           Data.Geometry.Polygon
import           Data.Geometry.Polygon.Convex (ConvexPolygon(..))
import           Data.Geometry.Vector
import qualified Data.List as List
import           Data.List.NonEmpty (NonEmpty(..), (<|))
import qualified Data.List.NonEmpty as NonEmpty
import           Data.Ord (comparing, Down(..))
import           Data.Semigroup.Foldable
convexHull            :: (Ord r, Num r)
                      => NonEmpty (Point 2 r :+ p) -> ConvexPolygon p r
convexHull (p :| []) = ConvexPolygon . fromPoints $ [p]
convexHull pts       = ConvexPolygon . fromPoints $ uh <> reverse lh
  where
    lh = case NonEmpty.nonEmpty (NonEmpty.init $ lowerHull pts) of
           Nothing       -> []
           Just (_:|lh') -> lh'
    uh = toList $ upperHull pts
                       
  
  
  
upperHull     ::  (Num r, Ord r) =>  NonEmpty (Point 2 r :+ p) -> NonEmpty (Point 2 r :+ p)
upperHull pts = repeatedly cmp steepestCwFrom s rest
  where
    (s:_ :+ rest) = extractMinimaBy cmp (NonEmpty.toList pts)
    cmp           = comparing (\(Point2 x y :+ _) -> (x, Down y))
                    
                    
                    
upperHull'     ::  (Num r, Ord r) =>  NonEmpty (Point 2 r :+ p) -> NonEmpty (Point 2 r :+ p)
upperHull' pts = pruneVertical $ repeatedly cmp steepestCwFrom s rest
  where
    (s:_ :+ rest) = extractMinimaBy cmp0 (NonEmpty.toList pts)
    cmp0          = comparing (\(Point2 x y :+ _) -> (x, Down y))
                    
    cmp           = comparing (^.core)
                    
                    
                    
lowerHull     ::  (Num r, Ord r) =>  NonEmpty (Point 2 r :+ p) -> NonEmpty (Point 2 r :+ p)
lowerHull pts = pruneVertical $ repeatedly cmp steepestCcwFrom s rest
  where
    (s:_ :+ rest) = extractMinimaBy cmp0 (NonEmpty.toList pts)
    cmp0          = comparing (\(Point2 x y :+ _) -> (x, Down y))
                    
    cmp           = comparing (^.core)
                    
                    
lowerHull'     :: (Num r, Ord r) => NonEmpty (Point 2 r :+ p) -> NonEmpty (Point 2 r :+ p)
lowerHull' pts = pruneVertical $ repeatedly cmp steepestCcwFrom s rest
  where
    (s:_ :+ rest) = extractMinimaBy cmp (NonEmpty.toList pts)
    cmp           = comparing (^.core)
steepestCcwFrom   :: (Ord r, Num r)
               => (Point 2 r :+ a) -> NonEmpty (Point 2 r :+ b)  -> Point 2 r :+ b
steepestCcwFrom p = List.minimumBy (ccwCmpAroundWith (Vector2 0 (-1)) p)
steepestCwFrom   :: (Ord r, Num r)
               => (Point 2 r :+ a) -> NonEmpty (Point 2 r :+ b)  -> Point 2 r :+ b
steepestCwFrom p = List.minimumBy (cwCmpAroundWith (Vector2 0 1) p)
repeatedly       :: (a -> a -> Ordering) -> (a -> NonEmpty a -> a) -> a -> [a] -> NonEmpty a
repeatedly cmp f = go
  where
    go m xs' = case NonEmpty.nonEmpty xs' of
      Nothing -> m :| []
      Just xs -> let p = f m xs
                 in m <| go p (NonEmpty.filter (\x -> p `cmp` x == LT) xs)
pruneVertical :: Eq r => NonEmpty (Point 2 r :+ p) -> NonEmpty (Point 2 r :+ p)
pruneVertical = either id id . foldr1With f (\q -> Left $ q:|[])
  where
    f p = \case
      Left (q:|qs) | p^.core.xCoord == q^.core.xCoord -> Left  (p :| qs)
                   | otherwise                        -> Right (p :| q:qs)
      Right pts                                       -> Right (p <| pts)
foldr1With     :: Foldable1 f => (a -> b -> b) -> (a -> b) -> f a -> b
foldr1With f b = go . toNonEmpty
  where
    go (x :| xs) = case NonEmpty.nonEmpty xs of
                     Nothing  -> b x
                     Just xs' -> x `f` (go xs')
extractMinimaBy     :: (a -> a -> Ordering) -> [a] -> [a] :+ [a]
extractMinimaBy cmp = \case
  []     -> [] :+ []
  (x:xs) -> first NonEmpty.toList $ foldr (\y (mins@(m:|_) :+ rest) ->
                                             case m `cmp` y of
                                               LT -> mins :+ y:rest
                                               EQ -> (y NonEmpty.<| mins) :+ rest
                                               GT -> (y:|[]) :+ NonEmpty.toList mins <> rest
                                          ) ((x:|[]) :+ []) xs