{-# LANGUAGE TemplateHaskell  #-}
module Data.Geometry.Arrangement.Internal where
import           Algorithms.BinarySearch
import           Control.Lens
import qualified Data.CircularSeq as CSeq
import           Data.Ext
import qualified Data.Foldable as F
import           Data.Geometry.Boundary
import           Data.Geometry.Box
import           Data.Geometry.Line
import           Data.Geometry.LineSegment
import           Data.Geometry.PlanarSubdivision
import           Data.Geometry.Point
import           Data.Geometry.Properties
import qualified Data.List as List
import           Data.Maybe
import           Data.Ord (Down(..))
import qualified Data.Vector as V
import           Data.Vinyl.CoRec
type ArrangementBoundary s e r = V.Vector (Point 2 r, VertexId' s, Maybe (Line 2 r :+ e))
data Arrangement s l v e f r = Arrangement {
    _inputLines             :: V.Vector (Line 2 r :+ l)
  , _subdivision            :: PlanarSubdivision s v e f r
  , _boundedArea            :: Rectangle () r
  , _unboundedIntersections :: ArrangementBoundary s l r
  } deriving (Show,Eq)
  
  
makeLenses ''Arrangement
type instance NumType   (Arrangement s l v e f r) = r
type instance Dimension (Arrangement s l v e f r) = 2
constructArrangement       :: (Ord r, Fractional r)
                           => proxy s
                           -> [Line 2 r :+ l]
                           -> Arrangement s l () (Maybe l) () r
constructArrangement px ls = let b  = makeBoundingBox ls
                             in constructArrangementInBox' px b ls
constructArrangementInBox            :: (Ord r, Fractional r)
                                     => proxy s
                                     -> Rectangle () r
                                     -> [Line 2 r :+ l]
                                     -> Arrangement s l () (Maybe l) () r
constructArrangementInBox px rect ls = let b  = makeBoundingBox ls
                                       in constructArrangementInBox' px (b <> rect) ls
constructArrangementInBox'            :: (Ord r, Fractional r)
                                      => proxy s
                                      -> Rectangle () r
                                      -> [Line 2 r :+ l]
                                      -> Arrangement s l () (Maybe l) () r
constructArrangementInBox' px rect ls =
    Arrangement (V.fromList ls) subdiv rect (link parts' subdiv)
  where
    subdiv = fromConnectedSegments px segs
                & rawVertexData.traverse.dataVal .~ ()
    (segs,parts') = computeSegsAndParts rect ls
computeSegsAndParts         :: forall r l. (Ord r, Fractional r)
                            => Rectangle () r
                            -> [Line 2 r :+ l]
                            -> ( [LineSegment 2 () r :+ Maybe l]
                               , [(Point 2 r, Maybe (Line 2 r :+ l))]
                               )
computeSegsAndParts rect ls = ( segs <> boundarySegs, parts')
  where
    segs         = map (&extra %~ Just)
                 . concatMap (\(l,ls') -> perLine rect l ls') $ makePairs ls
    boundarySegs = map (:+ Nothing) . toSegments . dupFirst $ map fst parts'
    dupFirst = \case []       -> []
                     xs@(x:_) -> xs ++ [x]
    parts'       = unBoundedParts rect ls
perLine       :: forall r l. (Ord r, Fractional r)
              => Rectangle () r -> Line 2 r :+ l -> [Line 2 r :+ l]
              -> [LineSegment 2 () r :+ l]
perLine b m ls = map (:+ m^.extra) . toSegments . rmDuplicates . List.sort $ vs <> vs'
  where
    rmDuplicates = map head . List.group
    vs  = mapMaybe (m `intersectionPoint`) ls
    vs' = maybe [] (\(p,q) -> [p,q]) . asA @(Point 2 r, Point 2 r)
        $ (m^.core) `intersect` (Boundary b)
intersectionPoint                   :: forall r l. (Ord r, Fractional r)
                                    => Line 2 r :+ l -> Line 2 r :+ l -> Maybe (Point 2 r)
intersectionPoint (l :+ _) (m :+ _) = asA @(Point 2 r) $ l `intersect` m
toSegments      :: Ord r => [Point 2 r] -> [LineSegment 2 () r]
toSegments ps = let pts = map ext $ ps in
  zipWith ClosedLineSegment pts (tail pts)
makeBoundingBox :: (Ord r, Fractional r) => [Line 2 r :+ l] -> Rectangle () r
makeBoundingBox = grow 1 . boundingBoxList' . intersections
intersections :: (Ord r, Fractional r) => [Line 2 r :+ l] -> [Point 2 r]
intersections = mapMaybe (uncurry intersectionPoint) . allPairs
sideIntersections      :: (Ord r, Fractional r)
                       => [Line 2 r :+ l] -> LineSegment 2 q r
                       -> [(Point 2 r, Line 2 r :+ l)]
sideIntersections ls s = let l   = supportingLine s :+ undefined
                         in List.sortOn fst . filter (flip onSegment s . fst)
                          . mapMaybe (\m -> (,m) <$> l `intersectionPoint` m) $ ls
unBoundedParts         :: (Ord r, Fractional r)
                       => Rectangle () r
                       -> [Line 2 r :+ l]
                       -> [(Point 2 r, Maybe (Line 2 r :+ l))]
unBoundedParts rect ls = [tl] <> t <> [tr] <> reverse r <> [br] <> reverse b <> [bl] <> l
  where
    sideIntersections' = over (traverse._2) Just . sideIntersections ls
    Sides t r b l       = fmap sideIntersections'      $ sides   rect
    Corners tl tr br bl = fmap ((,Nothing) . (^.core)) $ corners rect
link       :: Eq r => [(Point 2 r, a)] -> PlanarSubdivision s v (Maybe e) f r
           -> V.Vector (Point 2 r, VertexId' s, a)
link vs ps = V.fromList . map (\((p,x),(_,y)) -> (p,y,x)) . F.toList
           . fromJust' $ alignWith (\(p,_) (q,_) -> p == q) (CSeq.fromList vs) vs'
  where
    vs' = CSeq.fromList . map (\v -> (ps^.locationOf v,v) ) . V.toList
        $ boundaryVertices (outerFaceId ps) ps
    fromJust' = fromMaybe (error "Data.Geometry.Arrangement.link: fromJust")
makePairs :: [a] -> [(a,[a])]
makePairs = go
  where
    go []     = []
    go (x:xs) = (x,xs) : map (\(y,ys) -> (y,x:ys)) (go xs)
allPairs    :: [a] -> [(a,a)]
allPairs ys = go ys
  where
    go []     = []
    go (x:xs) = map (x,) xs ++ go xs
alignWith         :: (a -> b -> Bool) -> CSeq.CSeq a -> CSeq.CSeq b
                  -> Maybe (CSeq.CSeq (a,b))
alignWith p xs ys = CSeq.zipL xs <$> CSeq.findRotateTo (p (CSeq.focus xs)) ys
traverseLine       :: (Ord r, Fractional r)
                   => Line 2 r -> Arrangement s l v (Maybe e) f r -> [Dart s]
traverseLine l arr = let md    = findStart l arr
                         dup x = (x,x)
                     in maybe [] (List.unfoldr (fmap dup . follow arr)) md
findStart       :: forall s l v e f r. (Ord r, Fractional r)
                => Line 2 r -> Arrangement s l v (Maybe e) f r -> Maybe (Dart s)
findStart l arr = do
    (p,_)   <- asA @(Point 2 r, Point 2 r) $
                 l `intersect` (Boundary $ arr^.boundedArea)
    (_,v,_) <- findStartVertex p arr
    findStartDart (arr^.subdivision) v
findStartVertex       :: (Ord r, Fractional r)
                      => Point 2 r
                      -> Arrangement s l v e f r
                      -> Maybe (Point 2 r, VertexId' s, Maybe (Line 2 r :+ l))
findStartVertex p arr = do
    ss <- findSide p
    i  <- binarySearchVec (pred' ss) (arr^.unboundedIntersections)
    pure $ arr^.unboundedIntersections.singular (ix i)
  where
    Sides t r b l = sides'' $ arr^.boundedArea
    sides''       = fmap (\(ClosedLineSegment a c) -> LineSegment (Closed a) (Open c)) . sides
    findSide q = fmap fst . List.find (onSegment q . snd) $ zip [1..] [t,r,b,l]
    pred' ss (q,_,_) = let Just j = findSide q
                           x      = before (ss,p) (j,q)
                       in  x == LT || x == EQ
    before (i,p') (j,q') = case i `compare` j of
                                LT -> LT
                                GT -> GT
                                EQ | i == 2 || i == 3 -> Down p' `compare` Down q'
                                   | otherwise        -> p' `compare` q'
findStartDart      :: PlanarSubdivision s v (Maybe e) f r -> VertexId' s -> Maybe (Dart s)
findStartDart ps v = V.find (\d -> isJust $ ps^.dataOf d) $ incidentEdges v ps
    
follow       :: (Ord r, Num r) => Arrangement s l v e f r -> Dart s -> Maybe (Dart s)
follow arr d = V.find extends $ incidentEdges v ps
  where
    ps = arr^.subdivision
    v  = headOf d ps
    (up,vp) = over both (^.location) $ endPointData d ps
    extends d' = let wp = ps^.locationOf (headOf d' ps)
                 in d' /= twin d && ccw up vp wp == CoLinear