module Physics.Scenes.Stacks where

import Control.Lens
import Control.Monad
import Data.Proxy
import Physics.Engine.Class
import Physics.Scenes.Scene
import Physics.World.Class

box :: (PhysicsEngine e)
    => Proxy e
    -> (PENumber e, PENumber e)
    -> (PENumber e, PENumber e)
    -> PEPhysicalObj e
box p (x, y) (vx, vy) =
  makePhysicalObj p (vx, vy) 0 (x, y) 0 (2, 1)

boxFloor :: (PhysicsEngine e) => Proxy e -> PEPhysicalObj e
boxFloor p =
  makePhysicalObj p (0, 0) 0 (0, -6) 0 (0, 0)

box' :: (PhysicsEngine e)
     => Proxy e
     -> (PENumber e, PENumber e)
     -> (PENumber e, PENumber e)
     -> (PENumber e, PENumber e)
     -> PEExternalObj e
     -> PEWorldObj' e
box' p (w, h) center velocity =
  makeWorldObj p (box p center velocity) 0.2 (makeRectangleHull p w h)

boxFloor' :: (PhysicsEngine e) => Proxy e -> PEExternalObj e -> PEWorldObj' e
boxFloor' p =
  makeWorldObj p (boxFloor p) 0.2 (makeRectangleHull p 18 1)

boxStack :: (PhysicsEngine e)
         => Proxy e
         -> (PENumber e, PENumber e)
         -> (PENumber e, PENumber e)
         -> (PENumber e, PENumber e)
         -> PENumber e
         -> Int
         -> PEExternalObj e
         -> [PEWorldObj' e]
boxStack _ _ _ _ _ 0 _ = []
boxStack p size@(_, h) bottom vel spacing n ext =
  box' p size bottom vel ext : boxStack p size bottom' vel spacing (n - 1) ext
  where bottom' = bottom & _2 %~ (+ (h + spacing))

stacks :: (PhysicsEngine e)
       => Proxy e
       -> (PENumber e, PENumber e)
       -> (PENumber e, PENumber e)
       -> (PENumber e, PENumber e)
       -> PENumber e
       -> (Int, Int)
       -> PEExternalObj e
       -> [PEWorldObj' e]
stacks p size@(w, _) (center, bottom) vel spacing (n_w, n_h) ext =
  join . fmap f . take n_w $ iterate (+ w) leftmost
  where leftmost = center - (w * fromIntegral (n_w - 1) / 2)
        f left = boxStack p size (left, bottom) vel spacing n_h ext

world :: (PhysicsEngine e) => Proxy e -> PEExternalObj e -> PEWorld' e
world p ext = makeWorld p $ concat [ [boxFloor' p ext]
                                   , boxStack p (2, 2) (8  , -4.5) (-1, 0) 0 5 ext
                                   , boxStack p (2, 2) (5.5, -4.5) (-2, 0) 0 5 ext
                                   ]

world' :: (PhysicsEngine e) => Proxy e -> PEExternalObj e -> PEWorld' e
world' p ext = makeWorld p $ concat [ [boxFloor' p ext]
                                    , boxStack p (2, 2) (0, -4.5) (0, 0) 0 5 ext
                                    , [box' p (2, 2) (8, 0) (-6, 0) ext]
                                    ]

world'' :: (PhysicsEngine e) => Proxy e -> PEExternalObj e -> PEWorld' e
world'' p ext =
  makeWorld p (boxFloor' p ext : stacks p (1, 1) (0, -4.5) (0, 0) 1 (10, 10) ext)

world''' :: (PhysicsEngine e) => Proxy e -> PEExternalObj e -> PEWorld' e
world''' p ext =
  makeWorld p (boxFloor' p ext : stacks p (0.75, 0.75) (0, -4.5) (0, 0) 1 (15, 15) ext)

externals :: (PhysicsEngine e) => Proxy e -> [External]
externals p = [makeConstantAccel p (0, -2)]

contactBehavior :: (PhysicsEngine e) => Proxy e -> PEContactBehavior e
contactBehavior p = makeContactBehavior p 0.01 0.02

scene :: (PhysicsEngine e) => Proxy e -> PEExternalObj e -> Scene e
scene p ext = Scene (world p ext) (externals p) (contactBehavior p)

scene' :: (PhysicsEngine e) => Proxy e -> PEExternalObj e -> Scene e
scene' p ext = Scene (world' p ext) (externals p) (contactBehavior p)

scene'' :: (PhysicsEngine e) => Proxy e -> PEExternalObj e -> Scene e
scene'' p ext = Scene (world'' p ext) (externals p) (contactBehavior p)

scene''' :: (PhysicsEngine e) => Proxy e -> PEExternalObj e -> Scene e
scene''' p ext = Scene (world''' p ext) (externals p) (contactBehavior p)

makeScene :: (PhysicsEngine e) => (Int, Int) -> PENumber e -> Proxy e -> PEExternalObj e -> Scene e
makeScene dims spacing p ext = Scene w (externals p) (contactBehavior p)
  where w = makeWorld p (boxFloor' p ext : stacks p (0.2, 0.2) (0, -4.5) (0, 0) spacing dims ext)