{- |
Find which objects lie upon a line.
 -}
module Dodge.WorldEvent.ThingsHit
    ( thingsHit
    , thingHit
    , thingsHitLongLine
    , thingsHitExceptCr
    , thingsHitExceptCr3D'
    , thingsHitExceptCrLongLine
    )
    where
import Dodge.Data
import Dodge.Base
import Dodge.Zone
import Geometry
import Geometry.Vector3D

import qualified Data.IntMap.Strict as IM
import Data.List
import Data.Maybe
import Data.Bifunctor
{- List those objects that appear on a line.  -}
thingsHit
    :: Point2    -- ^ Line start point
    -> Point2    -- ^ Line end point
    -> World 
    -> [(Point2, Either Creature Wall)]
thingsHit sp ep w 
    | sp == ep  = []
    | otherwise = sortOn (dist sp . fst) (crs ++ wls)
  where 
    hitCrs = IM.elems 
        $ IM.filter (\cr -> circOnSeg sp ep (_crPos cr) (_crRad cr))
        $ _creatures w
    --    $ creaturesAlongLine sp ep w
    crPs     = map (\cr -> ssaTriPoint ep (_crPos cr) sp (_crRad cr)) hitCrs
    crs   = zip crPs (map Left hitCrs)
    hitWls   = wallsOnLine sp ep (IM.unions [f b $ f a $ _znObjects $ _wallsZone w | a<-[x-1,x,x+1]
                                                                      , b<-[y-1,y,y+1]])
    (x,y) = zoneOfPoint (0.5 *.* (sp +.+ ep))
    f i m = case IM.lookup i m of 
        Just val -> val
        _        -> IM.empty
    wls = zip (map (fromJust . hitPoint) hitWls) (map Right hitWls)
    hitPoint w' = uncurry (intersectSegSeg sp ep) (_wlLine w')
{- List those objects that appear on a line.  -}
thingsHit3D 
    :: Point3    -- ^ Line start point
    -> Point3    -- ^ Line end point
    -> World 
    -> [(Point3, Either Creature Wall)]
thingsHit3D sp ep w 
    | sp == ep  = []
    | otherwise = sortOn (dist3 sp . fst) (crs ++ wls)
  where 
    hitCrs = IM.elems 
        $ IM.filter (\cr -> circOnSeg (stripZ sp) (stripZ ep) (_crPos cr) (_crRad cr))
        $ _creatures w
    crPs     = map (\cr -> ssaTriPoint (stripZ ep) (_crPos cr) (stripZ sp) (_crRad cr)) hitCrs
    crs   = zip (map (addZ 20) crPs) (map Left hitCrs)
    hitWls   = wallsOnLine (stripZ sp) (stripZ ep) 
        (IM.unions [f b $ f a $ _znObjects $ _wallsZone w | a<-[x-1,x,x+1]
                                                                      , b<-[y-1,y,y+1]])
    (x,y) = zoneOfPoint (0.5 *.* (stripZ sp +.+ stripZ ep))
    f i m = case IM.lookup i m of 
        Just val -> val
        _        -> IM.empty
    wls = zip (map (addZ 20 . fromJust . hitPoint) hitWls) (map Right hitWls)
    hitPoint w' = uncurry (intersectSegSeg (stripZ sp) (stripZ ep)) (_wlLine w')
thingHit :: Point2 -> Point2 -> World -> Maybe (Point2, Either Creature Wall)
thingHit sp ep = listToMaybe . thingsHit sp ep
thingsHitExceptCr3D'
    :: Maybe Int -- ^ A possible creature ID
    -> Point3    -- ^ Line start point
    -> Point3    -- ^ Line end point
    -> World 
    -> [(Point2, Either Creature Wall)]
thingsHitExceptCr3D' mcid sp ep = fmap (first stripZ) . thingsHitExceptCr3D mcid sp ep
{- List objects that appear on a line.
Can filter out a creature.  -}
thingsHitExceptCr3D
    :: Maybe Int -- ^ A possible creature ID
    -> Point3    -- ^ Line start point
    -> Point3    -- ^ Line end point
    -> World 
    -> [(Point3, Either Creature Wall)]
thingsHitExceptCr3D Nothing    sp ep = thingsHit3D sp ep
thingsHitExceptCr3D (Just cid) sp ep = filter crNotCid . thingsHit3D sp ep
  where 
    crNotCid (_,Left cr) = _crID cr /= cid
    crNotCid _ = True
{- List objects that appear on a line.
Can filter out a creature.  -}
thingsHitExceptCr 
    :: Maybe Int -- ^ A possible creature ID
    -> Point2    -- ^ Line start point
    -> Point2    -- ^ Line end point
    -> World 
    -> [(Point2, Either Creature Wall)]
thingsHitExceptCr Nothing    sp ep = thingsHit sp ep
thingsHitExceptCr (Just cid) sp ep = filter crNotCid . thingsHit sp ep
  where 
    crNotCid (_,Left cr) = _crID cr /= cid
    crNotCid _ = True

thingsHitExceptCrLongLine 
    :: Maybe Int 
    -> Point2 
    -> Point2 
    -> World 
    -> [(Point2, Either Creature Wall)]
thingsHitExceptCrLongLine Nothing    sp ep = thingsHitLongLine sp ep
thingsHitExceptCrLongLine (Just cid) sp ep = filter crNotCid . thingsHitLongLine sp ep
  where 
    crNotCid (_,Left cr) = _crID cr /= cid
    crNotCid _ = True

thingsHitLongLine :: Point2 -> Point2 -> World -> [(Point2, Either Creature Wall)]
thingsHitLongLine sp ep w 
    | sp == ep  = []
    | otherwise = sortOn (dist sp . fst) (crs ++  wls)
  where 
    crs  = zip crPs (map Left hitCrs)
    hitCrs = IM.elems 
        . IM.filter (\cr -> circOnSeg sp ep (_crPos cr) (_crRad cr))
        $ _creatures w
    --   $ creaturesAlongLine sp ep w
    crPs = map (\cr -> ssaTriPoint ep (_crPos cr) sp (_crRad cr)) hitCrs
    wls = zip (map (fromJust . hitPoint) hitWls) (map Right hitWls)
    hitWls = wallsOnLine sp ep $ wallsAlongLine sp ep w
    hitPoint wl = uncurry (intersectSegSeg sp ep) (_wlLine wl)
