
module Level.ResolveIntersection where
#include "Utils.cpp"
import qualified Data.List as L
import qualified Gamgine.Math.BoxTree as BT
import qualified Gamgine.Math.Box as B
import Gamgine.Math.Vect as V
import Gamgine.Control ((?))
import qualified GameData.Entity as E
import qualified GameData.Level as LV
import qualified GameData.Enemy as EN
import qualified Entity.Intersect as EI
import qualified Entity.Bound as EB
import qualified Entity.Position as EP
import qualified Entity.Velocity as EV
import qualified Level.Reload as LR
import qualified Event as EV
import qualified Rendering.Renderer as RR
import qualified Defaults as DF
import qualified Convert.ToFileData as TF
import qualified Convert.ToGameData as TG
IMPORT_LENS_AS_LE


resolveIntersection :: EI.Intersection -> LV.Level -> Maybe EV.Event
resolveIntersection isect@(e1, e2, isects) level =
   resolve (LV.findEntityById e1 level) (LV.findEntityById e2 level) isects
   where
      resolve Nothing _ _ = ERROR("Couldn't find entity with id " ++ show e1)
      resolve _ Nothing _ = ERROR("Couldn't find entity with id " ++ show e2)
      resolve _ _ []      = Nothing

      resolve (Just e1) (Just e2) isects =
         case (e1, e2, isects) of
              (_, _, []) -> Nothing

              (E.Player   {}, E.Platform {}, isects) -> resolvePlayerWithPlatformIsect (e1, isectPositions BT.leaf1) e2
              (E.Platform {}, E.Player   {}, isects) -> resolvePlayerWithPlatformIsect (e2, isectPositions BT.leaf2) e1

              (E.Player {}, E.Enemy  {}, isects) -> resolvePlayerWithEnemyIsect (e1, isectPositions BT.leaf1) e2
              (E.Enemy  {}, E.Player {}, isects) -> resolvePlayerWithEnemyIsect (e2, isectPositions BT.leaf2) e1

              (E.Player {}, E.Star   {}, _) -> resolvePlayerWithStarIsect e1 e2
              (E.Star   {}, E.Player {}, _) -> resolvePlayerWithStarIsect e2 e1

              _ -> Nothing
         where
            isectPositions leaf = L.map (\i -> snd . leaf $ i) isects


      resolvePlayerWithStarIsect _ E.Star {E.starId = isectId, E.starPosition = pos} = Just . mkMultiEvent $ [
         mkUpdateEntityEvent $ \e ->
            case e of
                 E.Star {E.starId = id} | id == isectId -> e {E.starCollected = True}
                                        | otherwise     -> e
                 _ -> e,

         mkUpdateLevelEvent $ LE.modL LV.renderersL $ \rds ->
            RR.mkRenderer (RR.fadeOutStar pos 0) : rds ]


      resolvePlayerWithEnemyIsect (player, isectPos) enemy
         | playerKilled = Just . mkUpdateLevelEvent $ LR.reload
         | otherwise    = Just . mkMultiEvent $ [
            mkUpdateEntityEvent $ \e ->
               case e of
                    E.Enemy {E.enemyId = id} | id == enemyId -> e {E.enemyLiving = False}
                                             | otherwise     -> e
                    _ -> e,

            mkUpdateLevelEvent $ LE.modL LV.renderersL $ \rds ->
               RR.mkRenderer (RR.fadeOutEnemy enemyPos EN.enemySize) : rds ]
         where
            playerKilled = L.any (/= E.Bottom) isectPos
            enemyId      = E.enemyId enemy
            enemyPos     = EP.currentPosition enemy


      resolvePlayerWithPlatformIsect (player, isectPos) platform
         | isAnimated platform = moveAwayFromEachOther
         | otherwise           = movePlayerAwayFromPlatform

         where
            movePlayerAwayFromPlatform = Just . mkUpdateEntityEvent $ \e ->
               case e of
                    E.Player {} -> e {E.playerPosition = playerPos',
                                      E.playerOnBottom = onBottom',
                                      E.playerVelocity = playerVelo'}
                    _           -> e
               where
                  playerPos'   = playerPos + V.v3 ox oy 0


            moveAwayFromEachOther = Just . mkUpdateEntityEvent $ \e ->
               case e of
                    E.Player {} -> e {E.playerPosition = playerPos',
                                      E.playerOnBottom = onBottom',
                                      E.playerVelocity = playerVelo'}

                    E.Platform {E.platformId = id} | id == isectId -> EP.setCurrentPosition e platformPos'
                                                   | otherwise     -> e

                    _          -> e
               where
                  playerPos'    = playerPos + V.v3 ox1 oy1 0 + (onBottom ? platformVelo $ V.v3 0 0 0)
                  platformPos'  = platformPos - V.v3 ox2 oy2 0

                  (ox1, oy1) | onBottom  = (ox, oy)
                             | otherwise = (vx1 > 0 ? ox * (vx1/vx) $ 0, vy1 > 0 ? oy * (vy1/vy) $ 0)

                  (ox2, oy2) | onBottom  = (0, 0)
                             | otherwise = (vx2 > 0 ? ox * (vx2/vx) $ 0, vy2 > 0 ? oy * (vy2/vy) $ 0)

                  (vx, vy)      = (vx1 + vx2, vy1 + vy2)
                  (vx1:.vy1:._) = V.map abs playerVelo
                  (vx2:.vy2:._) = V.map abs platformVelo
                  isectId       = E.platformId platform


            playerVelo' = velo (E.playerVelocity player) isectPos
               where
                  velo v           []       = v
                  velo (vx:.vy:._) (ip:ips)
                     | ip == E.Top    = velo (V.v3 vx (-DF.gravity * 5) 0) ips
                     | ip == E.Bottom = velo (V.v3 vx 0 0) ips
                     | otherwise      = velo (V.v3 vx vy 0) ips


            onBottom' = onBottom || E.playerOnBottom player
            onBottom  = L.any (== E.Bottom) isectPos

            playerVelo    = EV.velocity player
            platformVelo  = EV.velocity platform

            playerPos     = EP.currentPosition player
            platformPos   = EP.currentPosition platform

            (ox, oy)      = minOverlapXY $ B.minOverlap playerBound platformBound
               where
                  minOverlapXY (x:.y:._)
                     | ax < ay   = (x, 0)
                     | ay < ax   = (0, y)
                     | otherwise = (x, y)
                     where
                        (ax, ay) = (abs x, abs y)

                  playerBound   = BT.asBox . EB.bound $ player
                  platformBound = BT.asBox . EB.bound $ platform

            isAnimated E.Platform {E.platformPosition = Right _} = True
            isAnimated E.Platform {E.platformPosition = Left  _} = False

      mkMultiEvent        = EV.MkMultiEvent
      mkUpdateLevelEvent  = EV.MkLevelEvent  . EV.UpdateLevel
      mkUpdateEntityEvent = EV.MkEntityEvent . EV.UpdateEntity
