{-
Functions controlling the movement of the screen camera: 
'_cameraCenter', '_cameraZoom', _cameraRot';
and the position that the character sees from: '_cameraViewFrom'.  -}
module Dodge.Update.Camera 
    ( updateCamera
    ) 
    where
import Dodge.Data
import Dodge.Base
import Dodge.Zone
import Dodge.Base.Window
import Dodge.Base.Collide
import Dodge.Config.KeyConfig
import Dodge.Item.Attachment.Data
import Geometry
--import Geometry.ConvexPoly
import Dodge.GameRoom

--import qualified Data.List.NonEmpty as NEL
import Control.Lens
--import Control.Applicative
import Data.Maybe
import qualified Data.Set as S
import qualified Data.IntMap.Strict as IM
import qualified SDL
--import Data.Monoid
--import Data.Semigroup
--import qualified Control.Foldl as L
{- Update the screen camera rotation and position, including any in rold scope/remote camera modifiers; 
update where your avatar's view is from.  -}
updateCamera :: World -> World
updateCamera = rotCam . moveCamera . updateScopeZoom 
{- Updte the center of the screen camera center and where your avatar's view is from in world. -}
moveCamera :: World -> World
moveCamera w = w 
    & cameraCenter .~ idealPos
    & cameraViewFrom .~ sightFrom
  where 
    aimRangeFactor 
        | _cameraZoom w == 0 = 0
        | otherwise = fromMaybe 0 (yourItem w ^? itAimingRange) / _cameraZoom w
    aimingMult 
        | SDL.ButtonRight `S.member` _mouseButtons w = 1
        | otherwise                                  = 0
    ypos = _crPos $ you w
    idealPos = camCenter 
        +.+ rotateV (_cameraRot w) (aimRangeFactor * aimingMult *.* _mousePos w)
    camCenter  = ypos +.+ scopeOffset
    scopeOffset = fromMaybe (V2 0 0) $ yourItem w ^? itAttachment . scopePos
    sightFrom 
        | fromMaybe False $ yourItem w ^? itAttachment . scopeIsCamera
                    = camCenter
        | otherwise = ypos

updateScopeZoom :: World -> World
updateScopeZoom w 
    | SDL.ButtonRight `S.member` _mouseButtons w = case w ^? scppoint of
        Just x 
           | x > 9     -> zoomInLongGun $ zoomInLongGun
                            $ w & creatures . ix 0 . crInv . ix (_crInvSel (_creatures w IM.! 0))
                                . itAttachment . scopeZoomChange -~ 2
           | x > 0     -> zoomInLongGun
                            $ w & creatures . ix 0 . crInv . ix (_crInvSel (_creatures w IM.! 0))
                                . itAttachment . scopeZoomChange -~ 1
           | x < -9    -> zoomOutLongGun $ zoomOutLongGun
                            $ w & creatures . ix 0 . crInv . ix (_crInvSel (_creatures w IM.! 0))
                                . itAttachment . scopeZoomChange +~ 2
           | x < 0     -> zoomOutLongGun $ zoomOutLongGun
                            $ w & creatures . ix 0 . crInv . ix (_crInvSel (_creatures w IM.! 0))
                                . itAttachment . scopeZoomChange +~ 1
           | otherwise -> w
        _                  -> w
    | otherwise = w & creatures . ix 0 . crInv . ix (_crInvSel (_creatures w IM.! 0))
                                . itAttachment %~ updateScope
  where 
    scppoint = creatures . ix 0 . crInv . ix (_crInvSel (_creatures w IM.! 0))
                          . itAttachment . scopeZoomChange
    updateScope (ItScope _ _ _ bl) = ItScope (V2 0 0) 0 1 bl
    updateScope otherAtt = otherAtt

zoomSpeed :: Float
zoomSpeed = 39/40

zoomInLongGun :: World -> World
zoomInLongGun w 
    | currentZoom < 8 = over (wpPointer . itAttachment . scopePos)
        (\p -> p +.+ (4-(4*zoomSpeed)) / currentZoom *.* mousep) 
        $ over (wpPointer . itAttachment . scopeZoom) (/ zoomSpeed)
        w
    | otherwise = w
  where 
    wpPointer = creatures . ix 0 . crInv . ix (_crInvSel (_creatures w IM.! 0))
    wp = _crInv (_creatures w IM.! 0) IM.! _crInvSel (_creatures w IM.! 0)
    Just currentZoom = wp ^? itAttachment . scopeZoom
    mousep = rotateV (_cameraRot w) $ _mousePos w

zoomOutLongGun :: World -> World
zoomOutLongGun w 
    | currentZoom > 0.5 = over (wpPointer . itAttachment . scopeZoom) (* zoomSpeed) w
    | otherwise = w
  where 
    wpPointer = creatures . ix 0 . crInv . ix (_crInvSel (_creatures w IM.! 0))
    wp = _crInv (_creatures w IM.! 0) IM.! _crInvSel (_creatures w IM.! 0)
    Just currentZoom = wp ^? itAttachment . scopeZoom

rotCam :: World -> World
rotCam = rotateCameraL 
    . rotateCameraR 
    . zoomCamIn . zoomCamOut 
    . autoZoomCam

rotateCameraL :: World -> World
rotateCameraL w 
    | rotateCameraPlusKey (_keyConfig w) `S.member` _keys w
        = w & cameraRot +~ 0.015
            & creatures . ix 0 . crInv . ix (_crInvSel (_creatures w IM.! 0))
                                . itAttachment . scopePos %~ rotateV 0.015
    | otherwise = w
rotateCameraR :: World -> World
rotateCameraR w | rotateCameraMinusKey (_keyConfig w) `S.member` _keys w
                    = w & cameraRot -~ 0.015
                        & creatures . ix 0 . crInv . ix (_crInvSel (_creatures w IM.! 0))
                                . itAttachment . scopePos %~ rotateV (-0.015)
                | otherwise = w
zoomCamIn :: World -> World
zoomCamIn w | zoomInKey (_keyConfig w) `S.member` _keys w
                    = w {_cameraZoom = _cameraZoom w + 0.01}
            | otherwise = w
zoomCamOut :: World -> World
zoomCamOut w | zoomOutKey (_keyConfig w) `S.member` _keys w
                    = w {_cameraZoom = max (_cameraZoom w - 0.01) 0.01}
             | otherwise = w

zoomFromItem 
    :: ItZoom
    -> Float -- ^ Furthest viewable distance
    -> Float
zoomFromItem ItZoom {_itZoomMax = zMax, _itZoomMin = zMin, _itZoomFac = zFac}
    = min zMax . max zMin . (zFac *)
zoomNoItem 
    :: Float -- ^ Furthest viewable distance
    -> Float
zoomNoItem = min 20 . max 0.2
{- Automatically sets the zoom of the camera according to the surrounding walls.  -}
autoZoomCam :: World -> World
autoZoomCam w = over cameraZoom changeZoom w
  where 
    camPos = _cameraViewFrom w
    wallZoom = farWallDist' camPos w
    idealZoom 
        | SDL.ButtonRight `S.member` _mouseButtons w 
            = theScopeZoom * maybe zoomNoItem zoomFromItem (yourItem w ^? itAimZoom) wallZoom
        | otherwise
            = maybe zoomNoItem zoomFromItem (yourItem w ^? itZoom) wallZoom
    changeZoom curZoom 
      | curZoom > idealZoom + 0.01 = ((zoomOutSpeed-1)*curZoom + idealZoom) / zoomOutSpeed
      | curZoom < idealZoom - 0.01 = ((zoomInSpeed -1)*curZoom + idealZoom) / zoomInSpeed
      | otherwise                  = idealZoom
    -- these speeds are inverted, larger means slower
    zoomInSpeed  = 25
    zoomOutSpeed = 15
    theScopeZoom = fromMaybe 1 $ yourItem w ^? itAttachment . scopeZoom

farWallDist' :: Point2 -> World -> Float
farWallDist' p w = (winFac /) . min maxViewDistance $ ssfold (> maxViewDistance) findMax 1 vps
  where
    findMax curMax pout = max curMax $ dist p $ collidePointUpToIndirectMinDist p pout curMax wos
    hw = halfWidth w
    hh = halfHeight w
    winFac = min hw hh
    vps = concatMap _grViewpoints grs
    grs = filter (pointInOrOnPolygon p . _grBound) (_gameRooms w)
    wos = wallsOnScreen w

--farWallDist :: Point2 -> World -> Float
--farWallDist p w = (winFac /) $ fromMaybe maxViewDistance 
--    $ L.fold (L.premap (dist p) L.maximum) vdists
--  where
--    hw = halfWidth w
--    hh = halfHeight w
--    winFac = min hw hh
--    vdists :: [Point2]
--    vdists = map (flip (collidePointUpToIndirect p) wos) vps
--    vps = concatMap _grViewpoints grs
--    grs = filter (pointInOrOnPolygon p . _grBound) (_gameRooms w)
--    wos = wallsOnScreen w

maxViewDistance :: Float
maxViewDistance = 800
