
module AppData where
#include "Utils.cpp"
import qualified Graphics.UI.GLFW as GLFW
import qualified Data.IORef as R
import qualified Data.List as L
import Data.Maybe (fromMaybe)
import qualified Control.Monad.State as ST
import qualified Gamgine.Utils as GU
import qualified GameData.Data as GD
import qualified GameData.Level as LV
import qualified GameData.Entity as E
import qualified Defaults as DF
import qualified Gamgine.State.RenderState as RS
import qualified Gamgine.State.State as S
import qualified Gamgine.State.StateTree as SS
import Gamgine.State.StateTree (enterWhen, leaveWhen, adjacents, StateTree(..), StateTransition(..))
import Gamgine.State.StateTreeZipper as SZ
import qualified Gamgine.State.KeyInfo as KI
import qualified Gamgine.State.MouseInfo as MI
import qualified Gamgine.State.InputInfo as II
import Gamgine.State.InputInfo (Modifier(..), InputState(..))
import qualified States.GameRunning as GR
import qualified States.EditModeRunning as EM
import qualified States.MovingEntity as ME
import qualified States.CreatingPlatform as CP
import qualified States.ResizingPlatform as RP
import qualified States.DefiningAnimation as DA
import qualified States.IntroRunning as IR
IMPORT_LENS_AS_LE

data AppData = AppData {
   window           :: GLFW.Window,
   windowSize       :: (Int, Int),
   frustumSize      :: (Double, Double),
   orthoScale       :: Double,
   renderRessources :: RS.Ressources,
   levelsLoadedFrom :: FilePath,
   saveLevelsTo     :: FilePath,
   appMode          :: AppMode,
   gameData         :: GD.Data,
   stateTree        :: SZ.Zipper GD.Data
   }

LENS(window)
LENS(windowSize)
LENS(frustumSize)
LENS(orthoScale)
LENS(renderRessources)
LENS(levelsLoadedFrom)
LENS(saveLevelsTo)
LENS(appMode)
LENS(gameData)
LENS(stateTree)

newAppData :: GLFW.Window -> GD.Data -> FilePath -> FilePath -> AppMode -> AppData
newAppData win gameData levelsLoadedFrom saveLevelsTo appMode = AppData {
   window           = win,
   windowSize       = (0,0),
   frustumSize      = (0,0),
   orthoScale       = DF.orthoScale,
   renderRessources = RS.emptyRessources,
   levelsLoadedFrom = levelsLoadedFrom,
   saveLevelsTo     = saveLevelsTo,
   appMode          = appMode,
   gameData         = gameData,
   stateTree        = SZ.zipper $
      if appMode == EditMode
         then SS.root EM.mkEditModeRunningState
                 [Branch {state     = ME.mkMovingEntityState,
                          enterWhen = ByMouseWithMod GLFW.MouseButton'1 Pressed Ctrl,
                          leaveWhen = ByMouse GLFW.MouseButton'1 Released,
                          adjacents = []},
                  Branch {state     = RP.mkResizingPlatformState,
                          enterWhen = ByMouseWithMod GLFW.MouseButton'1 Pressed Shift,
                          leaveWhen = ByMouse GLFW.MouseButton'1 Released,
                          adjacents = []},
                  Branch {state     = CP.mkCreatingPlatformState,
                          enterWhen = ByMouse GLFW.MouseButton'1 Pressed,
                          leaveWhen = ByMouse GLFW.MouseButton'1 Released,
                          adjacents = []},
                  Branch {state     = DA.mkDefiningAnimationState,
                          enterWhen = ByKey (GLFW.Key'U) Pressed,
                          leaveWhen = ByKey (GLFW.Key'U) Pressed,
                          adjacents = []}]
         else SS.root IR.mkIntroRunningState
                 [Branch {state     = GR.mkGameRunningState,
                          enterWhen = ByKey (GLFW.Key'Space) Pressed,
                          leaveWhen = NoTransition,
                          adjacents = []}]
   }

data AppMode = GameMode | EditMode deriving Eq

type AppDataRef = R.IORef AppData
type AppST      = ST.StateT AppDataRef IO


runAppST :: AppST a -> AppDataRef -> IO (a, AppDataRef)
runAppST = ST.runStateT


update :: AppData -> AppData
update app = applyToState f app
   where
      f = S.update $ LE.getL currentStateL app


render :: Double -> AppData -> IO AppData
render nextFrameFraction app = applyToStateIO f app
   where
      f      = (S.render $ LE.getL currentStateL app) rstate
      rstate = RS.RenderState nextFrameFraction (renderRessources app) (frustumSize app)


handleKeyEvent :: KI.KeyInfo -> AppData -> AppData
handleKeyEvent ki app =
   let (gdata', stree') = SZ.handleKeyEvent ki (gameData app) (stateTree app)
       in app {gameData = gdata', stateTree = stree'}


handleMouseEvent :: MI.MouseInfo -> AppData -> AppData
handleMouseEvent mi app =
   let (gdata', stree') = SZ.handleMouseEvent mi (gameData app) (stateTree app)
       in app {gameData = gdata', stateTree = stree'}


handleMouseMoved :: II.MousePos -> AppData -> AppData
handleMouseMoved mp app = applyToState f app
   where
      f = (S.mouseMoved $ LE.getL currentStateL app) mp


applyToState :: (GD.Data -> (GD.Data, S.State GD.Data)) -> AppData -> AppData
applyToState f app = LE.setL currentStateL state' $ app {gameData = gdata'}
   where
      (gdata', state') = f $ gameData app


applyToStateIO :: (GD.Data -> IO (GD.Data, S.State GD.Data)) -> AppData -> IO AppData
applyToStateIO f app = do
   (gdata', state') <- f $ gameData app
   return (LE.setL currentStateL state' $ app {gameData = gdata'})


currentStateL = LE.lens getCurrentState setCurrentState
   where
      getCurrentState = state . SZ.current . stateTree
         where
            state (SS.Branch s _ _ _) = s

      setCurrentState state = LE.modL stateTreeL $ SZ.replace state


currentLevelL  = GD.currentLevelL . gameDataL
activeLayerL   = LV.activeLayerL . currentLevelL
inactiveLayers = LV.inactiveLayers . LE.getL currentLevelL
