{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
module Data.Geometry.QuadTree
                             
                             
                             
                             
                             
                             
                             where
import           Control.Lens (makeLenses, (^.), (.~), (&), (^?!), ix, view)
import           Data.Ext
import qualified Data.Foldable as F
import           Data.Geometry.Box
import           Data.Geometry.Point
import           Data.Geometry.QuadTree.Cell
import           Data.Geometry.QuadTree.Quadrants
import           Data.Geometry.QuadTree.Split
import           Data.Geometry.QuadTree.Tree (Tree(..))
import qualified Data.Geometry.QuadTree.Tree as Tree
import           Data.Geometry.Vector
import           Data.Intersection
import           Data.List.NonEmpty (NonEmpty(..))
import           Data.Tree.Util (TreeNode(..), levels)
import           GHC.Generics (Generic)
data QuadTree v p r = QuadTree { _startingCell  :: !(Cell r)
                               , _tree          :: !(Tree v p)
                               }
                    deriving (Show,Eq,Generic,Functor,Foldable,Traversable)
makeLenses ''QuadTree
withCells    :: (Fractional r, Ord r) => QuadTree v p r -> QuadTree (v :+ Cell r) (p :+ Cell r) r
withCells qt = qt&tree .~ withCellsTree qt
withCellsTree                :: (Fractional r, Ord r)
                             => QuadTree v p r -> Tree (v :+ Cell r) (p :+ Cell r)
withCellsTree (QuadTree c t) = Tree.withCells c t
leaves :: QuadTree v p r -> NonEmpty p
leaves = Tree.leaves . view tree
perLevel :: QuadTree v p r -> NonEmpty (NonEmpty (TreeNode v p))
perLevel = levels . Tree.toRoseTree . view tree
buildOn            :: Cell r -> (Cell r -> i -> Tree v p) -> i -> QuadTree v p r
buildOn c0 builder = QuadTree c0 . builder c0
build     :: (Fractional r, Ord r) => (Cell r -> i -> Split i v p) -> Cell r -> i -> QuadTree v p r
build f c = buildOn c (Tree.build f)
fromPointsBox   :: (Fractional r, Ord r)
                 => Cell r -> [Point 2 r :+ p] -> QuadTree () (Maybe (Point 2 r :+ p)) r
fromPointsBox c = buildOn c Tree.fromPoints
fromPoints     :: (RealFrac r, Ord r)
               => NonEmpty (Point 2 r :+ p) -> QuadTree () (Maybe (Point 2 r :+ p)) r
fromPoints pts = buildOn c Tree.fromPoints (F.toList pts)
  where
    c = fitsRectangle $ boundingBoxList (view core <$> pts)
findLeaf                                       :: (Fractional r, Ord r)
                                               => Point 2 r -> QuadTree v p r -> Maybe (p :+ Cell r)
findLeaf q (QuadTree c0 t) | q `intersects` c0  = Just $ findLeaf' c0 t
                           | otherwise          = Nothing
  where
    
    
    findLeaf' c = \case
      Leaf p    -> p :+ c
      Node _ qs -> let quad = quadrantOf q c
                   in findLeaf' ((splitCell c)^?!ix quad) (qs^?!ix quad)
fromZeros :: (Fractional r, Ord r, Num a, Eq a, v ~ Quadrants Sign)
          => Cell r -> (Point 2 r -> a) -> QuadTree v (Either v Sign) r
fromZeros = fromZerosWith (limitWidthTo (-1))
fromZerosWith            ::  (Fractional r, Ord r, Eq a, Num a)
                         => Limiter r (Corners Sign) (Corners Sign) Sign
                         -> Cell r
                         -> (Point 2 r -> a)
                         -> QuadTree (Quadrants Sign) (Signs Sign) r
fromZerosWith limit c0 f = fromZerosWith' limit c0 (fromSignum f)
type Signs sign = Either (Corners sign) sign
fromZerosWith'           :: (Eq sign, Fractional r, Ord r)
                         => Limiter r (Corners sign) (Corners sign) sign
                         -> Cell r
                         -> (Point 2 r -> sign)
                         -> QuadTree (Quadrants sign) (Signs sign) r
fromZerosWith' limit c0 f = build (limit $ shouldSplitZeros f) c0 (f <$> cellCorners c0)
data Sign = Negative | Zero | Positive deriving (Show,Eq,Ord)
fromOrdering :: Ordering -> Sign
fromOrdering = \case
    LT -> Negative
    EQ -> Zero
    GT -> Positive
fromSignum   :: (Num a, Eq a) => (b -> a) -> b -> Sign
fromSignum f = \x -> case signum (f x) of
                       -1 -> Negative
                       0  -> Zero
                       1  -> Positive
                       _  -> error "absurd: fromSignum"
shouldSplitZeros :: forall r sign. (Fractional r, Eq sign)
                 => (Point 2 r -> sign) 
                 -> Splitter r
                             (Quadrants sign) 
                             (Quadrants sign) 
                             sign
shouldSplitZeros f (Cell w' p) qs@(Quadrants nw ne se sw) | all sameSign qs = No ne
                                                          | otherwise       = Yes qs qs'
  where
    m = fAt rr rr
    n = fAt rr ww
    e = fAt ww rr
    s = fAt rr 0
    w = fAt 0  rr
    sameSign = (== ne)
    
    qs' = Quadrants (Quadrants nw n m w)
                    (Quadrants n ne e m)
                    (Quadrants m e se s)
                    (Quadrants w m s sw)
    r     = w' - 1
    rr    = pow r
    ww    = pow w'
    fAt x y = f $ p .+^ Vector2 x y
isZeroCell   :: (Eq sign) => sign 
             -> Either v sign -> Bool
isZeroCell z = \case
    Left _  -> True 
    Right s -> s == z
completeTree    :: (Fractional r, Ord r) => Cell r -> QuadTree () () r
completeTree c0 =
    build (\_ w -> if w == 0 then No () else Yes () (pure $ w - 1)) c0 (c0^.cellWidthIndex)