{-# LANGUAGE FlexibleContexts           #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses      #-}
{-# LANGUAGE TypeFamilies               #-}

module Apecs.Physics.Gloss
  ( BodyPicture (..), Camera (..),
    toPicture, drawWorld, applyView, mouseToWorld, v2ToTuple,
    defaultSimulate,
  ) where

import           Apecs
import           Apecs.Physics
import           Data.Foldable                        (fold)
import           Data.Semigroup                       (Semigroup(..))
import qualified Graphics.Gloss                       as G
import           Graphics.Gloss.Geometry.Angle        (radToDeg)
import qualified Graphics.Gloss.Interface.IO.Simulate as GS

newtype BodyPicture = BodyPicture G.Picture deriving Monoid
instance Semigroup BodyPicture where
  BodyPicture pa <> BodyPicture pb = BodyPicture (mappend pa pb)

instance Component BodyPicture where
  type Storage BodyPicture = Map BodyPicture

data Camera = Camera
  { gvOffset :: V2 Double
  , gvScale  :: Double
  }

instance Semigroup Camera where
  Camera p1 z1 <> Camera p2 z2 = Camera (p1 + p2) (z1 * z2)
instance Monoid Camera where
  mempty = Camera 0 1
  mappend = (<>)

instance Component Camera where
  type Storage Camera = Global Camera

applyView :: Camera -> G.Picture -> G.Picture
applyView (Camera (V2 x y) scale) =
  G.Scale (realToFrac scale) (realToFrac scale) .  G.Translate (realToFrac . negate $ x) (realToFrac . negate $ y)

mouseToWorld :: (Float,Float) -> Camera -> V2 Double
mouseToWorld (x,y) (Camera offset scale) = (/scale) <$> (V2 (realToFrac x) (realToFrac y))-offset

toPicture :: Convex -> G.Picture
toPicture (Convex [V2 x y] radius) = G.Translate (realToFrac x) (realToFrac y) $ G.Circle (realToFrac radius)
toPicture (Convex [a,b] _) = G.Line [v2ToTuple a, v2ToTuple b]
toPicture (Convex verts _) = G.Polygon (v2ToTuple <$> verts)

v2ToTuple :: V2 Double -> (Float, Float)
v2ToTuple (V2 x y) = (realToFrac x, realToFrac y)

drawWorld
  :: ( Has w IO Physics
     , Has w IO BodyPicture
     , Has w IO Camera)
  => System w G.Picture
drawWorld = do
  pics <- flip cfold [] $
    \ps ((Position (V2 x y), Angle theta, BodyPicture pic)) ->
      (G.Translate (realToFrac x) (realToFrac y) .
       G.Rotate (negate . radToDeg . realToFrac $ theta) $
       pic) : ps
  view <- get global
  return . applyView view . G.pictures $ pics

defaultSimulate
  :: ( Has w IO Physics
     , Has w IO BodyPicture
     , Has w IO Camera)
  => w -> String -> IO ()
defaultSimulate w name =
  GS.simulateIO (GS.InWindow name (640,480) (100,100)) G.black 60 w render stepper
    where
      render w       = runSystem drawWorld w
      stepper _ dT w = runSystem (stepPhysics (1/60)) w >> return w