{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE RankNTypes #-}
-- | deals with placement of objects within the world
-- after they have had their coordinates set by the layout
module Dodge.Placement.PlaceSpot
    ( placeSpot
    ) where
import Dodge.Placement.Shift
import Dodge.Data
--import Dodge.Path
import Dodge.Placement.PlaceSpot.Block
import Dodge.Placement.PlaceSpot.TriggerDoor
import Dodge.Default.Wall
import Dodge.ShiftPoint
import Dodge.Base.NewID
import Geometry
import qualified IntMapHelp as IM
import Color

import Data.Foldable
import Data.Maybe
import System.Random
import Control.Monad.State
import Control.Lens
import qualified Data.IntSet as IS
import Data.Bifunctor

-- when placing a placement, we update the world and the room and assign an id
-- to the placement
placeSpot :: (World,Room) -> Placement -> ( (World,Room), [Placement] )
placeSpot (w,rm) plmnt = case plmnt of
    Placement{_plSpot = PSRoomRand i f} -> placeSpotRoomRand rm i f plmnt w
    Placement{_plSpot = PSPos extract eff fallback} -> placeSpotUsingLink w rm plmnt extract eff fallback
    Placement{} -> placePlainPSSpot w rm plmnt shift
    PlacementUsingPos p subpl -> placeSpot (w,rm) (subpl (shiftPoint3By shift p))
    RandomPlacement rplmnt -> placeRandomPlacement rplmnt w rm
    PickOnePlacement i pl -> ((placePickOne i pl rm w, rm), [])
  where
    shift = _rmShift rm

placePickOne :: Int -> Placement -> Room -> World -> World
placePickOne i pl rm w = w & genPlacements %~ IM.insertWith (++) i [(pl, fromJust (_rmMID rm))]

placeRandomPlacement :: State StdGen Placement -> World -> Room -> ((World,Room),[Placement])
placeRandomPlacement rplmnt w rm = placeSpot (w & randGen .~ g,rm) plmnt'
  where
    (plmnt', g) = runState rplmnt (_randGen w)

placePlainPSSpot :: World -> Room -> Placement -> DPoint2 -> ((World,Room),[Placement])
placePlainPSSpot w rm plmnt shift = 
    let (i,w') = placeSpotID (shiftPSBy shift (_plSpot plmnt)) (_plType plmnt) w
        newplmnt = plmnt & plMID ?~ i
    in maybe ((w',rm),[newplmnt]) (recrPlace newplmnt w') (_plIDCont plmnt w' newplmnt)
  where
    recrPlace newplmnt w' pl = let (wr,newplmnts) = placeSpot (w',rm) pl
                    in (wr,newplmnt:newplmnts)

-- this should be tidied up
placeSpotUsingLink :: World 
    -> Room 
    -> Placement 
    -> (RoomPos -> Room -> Maybe (PlacementSpot,RoomPos))
    -> (RoomPos -> Room -> Room)
    -> Maybe Placement
    -> ((World, Room), [Placement])
placeSpotUsingLink w rm plmnt extract eff fallback = case searchedPoss (_rmPos rm) of
    Just (ps,rmposs) -> placeSpot (w, eff (head rmposs) $ rm & rmPos .~ rmposs) (plmnt & plSpot .~ ps)
    Nothing -> case fallback of
        Nothing -> ((w,rm),[plmnt])
        Just plmnt' -> placeSpot (w,rm) plmnt'
  where
    searchedPoss [] = Nothing
    searchedPoss (pos:poss) = case extract pos rm of
        Nothing -> second (pos:) <$> searchedPoss poss
        Just (ps,rmpos) -> Just ( ps,rmpos:poss)

placeSpotRoomRand :: Room -> Int -> (DPoint2 -> PlacementSpot) 
    -> Placement -> World -> ((World,Room),[Placement])
placeSpotRoomRand rm i f plmnt w =
    let (ps,g) = runState (_rmRandPSs rm !! i) $_randGen w
    in placeSpot (w & randGen .~ g,rm) (plmnt & plSpot .~ f ps)

-- the Int here is some id that is assigned when the placement is placed
placeSpotID :: PlacementSpot -> PSType -> World -> (Int, World)
placeSpotID ps pt w = case pt of
    PutTrigger  cnd         -> plNewID   triggers cnd w
    PutMod      mdi         -> plNewUpID modifications mdID mdi w
    PutProp     prp         -> plNewUpID props pjID (mvProp p rot prp) w
    PutButton   bt          -> plNewUpID buttons btID (mvButton p rot bt) w
    PutTerminal tm          -> plNewUpID terminals tmID tm w
    PutFlIt     itm         -> plNewUpID floorItems flItID (createFlIt p rot itm) w
    PutCrit     cr          -> plNewUpID creatures crID (mvCr p rot cr) w
    PutForeground fs        -> plNewUpID shapes fsID (mvFS p rot fs) w
    PutMachine  pps mc      -> plMachine (map doShift pps) mc p rot w
    PutLS       ls          -> plNewUpID lightSources lsID (mvLS p' rot ls) w
    PutPPlate   pp          -> plNewUpID pressPlates ppID (mvPP p rot pp) w
    RandPS      rgn         -> evaluateRandPS rgn ps w
    PutDoor     col eo f   pss -> plDoor col eo f (map (bimap doShift doShift) pss) w
    PutCoord    cp          -> plNewID   coordinates (doShift cp) w
    PutSlideDr  wl dr eo off  a   b 
                            -> plSlideDoor wl dr eo off (doShift a) (doShift b) w
    PutBlock    bl wl  ps'  -> plBlock (map doShift ps') (bl & blPos %~ doShift & blDir .~ rot)
                                    wl w
    PutLineBlock wl wdth a b     -> plLineBlock wl wdth (doShift a) (doShift b) w
    PutWall     qs  wl      -> (0,placeWallPoly (map doShift qs) wl w)
    PutNothing              -> (0,w)
    PutID i                 -> (i, w)
    PutWorldUpdate f        -> (0, w & f ps)
    PutUsingGenParams f     ->  let (w',pt') = f w
                                in placeSpotID ps pt' w'
  where 
    p@(V2 px py) = _psPos ps
    p' = V3 px py 0
    rot = _psRot ps
    doShift = shiftPointBy (p,rot)

evaluateRandPS :: State StdGen PSType -> PlacementSpot -> World -> (Int,World)
evaluateRandPS rgen ps w = placeSpotID ps evaluatedType (set randGen g w)
  where 
    (evaluatedType, g) = runState rgen (_randGen w)

placeWallPoly :: [Point2] -> Wall -> World -> World
placeWallPoly ps wl = -- rmCrossPaths . 
    over walls (placeWalls ps wl)
--  where
--    rmCrossPaths w = foldr (uncurry obstructPathsCrossing) w $ loopPairs ps

placeWalls :: [Point2] -> Wall -> IM.IntMap Wall -> IM.IntMap Wall
placeWalls qs wl wls = foldl' (addPane wl) wls pairs
  where 
    (p:ps) = orderPolygon qs
    pairs = zip (ps ++ [p]) (p:ps)

addPane :: Wall -> IM.IntMap Wall -> (Point2,Point2) -> IM.IntMap Wall
addPane wl wls l = IM.insert wlid (wl { _wlLine = l, _wlID = wlid }) wls
  where
    wlid = IM.newKey wls

---- | place an new object into an intmap and update its id
--plNewUpID :: ALens' World (IM.IntMap a)
--    -> ALens' a Int
--    -> a
--    -> World
--    -> (Int,World)
--plNewUpID l li x w = (i,w & l #%~ IM.insert i (x & li #~ i))
--  where
--    i = IM.newKey $ w ^# l

mvProp :: Point2 -> Float -> Prop -> Prop
mvProp p a = (pjRot +~ a) . (prPos %~ ( (p +.+) . rotateV a ))

mvButton :: Point2 -> Float -> Button -> Button
mvButton p a = (btRot +~ a) . (btPos %~ ( (p +.+) . rotateV a ))

{- Creates a floor item at a given point.-}
createFlIt :: Point2 -> Float -> Item -> FloorItem
createFlIt p rot itm = FlIt { _flItPos = p , _flItRot = rot , _flItID = 0 , _flIt = itm }

mvPP :: Point2 -> Float -> PressPlate -> PressPlate
mvPP p rot pp = pp {_ppPos = p,_ppRot = rot}

mvCr :: Point2 -> Float -> Creature -> Creature
mvCr p rot cr = cr {_crPos = p,_crOldPos = p,_crDir = rot}

mvFS :: Point2 -> Float -> ForegroundShape -> ForegroundShape
mvFS p a = (fsDir +~ a) . (fsPos %~ ( (p +.+) . rotateV a ))

plMachine :: [Point2] -> Machine -> Point2 -> Float -> World -> (Int,World)
plMachine wallpoly mc p rot gw = (mcid
    , gw & machines  %~ addMc
         & walls     %~ placeMachineWalls color wallpoly mcid wlid
    )
  where 
    color = _mcColor mc
    w' = gw
    mcid = IM.newKey $ _machines w'
    wlid = IM.newKey $ _walls w'
    wlids = IS.fromList [wlid .. wlid + length wallpoly - 1]
    --addMc = IM.insert mcid (mc {_mcPos = centroid wallpoly,_mcDir = rot,_mcID = mcid, _mcWallIDs = wlids})
    addMc = IM.insert mcid (mc {_mcPos = p,_mcDir = rot,_mcID = mcid, _mcWallIDs = wlids})

-- TODO correctly remove/shift pathfinding lines (removePathsCrossing)
placeMachineWalls :: Color -> [Point2] -> Int -> Int -> IM.IntMap Wall -> IM.IntMap Wall
placeMachineWalls col poly mcid wlid = flip (foldr f) $ zip [wlid..] $ loopPairs poly
  where
    f (wid,l) = IM.insert wid baseWall{_wlID = wid, _wlLine = l}
    baseWall = defaultMachineWall 
        & wlColor .~ col
        & wlStructure . wsMachine .~ mcid
        & wlTouchThrough .~ True

mvLS :: Point3 -> Float -> LightSource -> LightSource
mvLS (V3 x y z) rot ls = ls & lsParam . lsPos .~ V3 x y z +.+.+ startPos
    & lsDir .~ rot
  where
    startPos = onXY (rotateV rot) $ _lsPos (_lsParam ls)
