```-- |
-- Module      :  HGE2D.Geometry
-- Copyright   :  (c) 2016 Martin Buck
--
-- Containing geometrical functions

module HGE2D.Geometry where

import Data.List

import HGE2D.Math
import HGE2D.Types
import HGE2D.Datas
import HGE2D.Classes

import Safe

---TODO rewrite most / all functions to use classes
---TODO use typedefs

-- | Transform an angle in radians to an angle in degrees

-- | Transform an angle in degrees to an angle in radians
deg2rad deg = deg * pi / 180

-- | Calculate angle in radians between two positions in space, from the first to the secind
radRealPos p1 p2 = atan2 dY dX
where
dX = (fst p2) - (fst p1)
dY = (snd p2) - (snd p1)

-- | Calculate angle of a velocity
velAngle v = atan2 (fst v) (snd v)

-- | Distance between two positions
distance :: (Positioned a, Positioned b) => a -> b -> Double
distance x y = sqrt \$ distanceSqr x y

-- | Squared distance between two positions
--   Faster than calculating the distance. Can be used to e.g. compare distances cheaply
distanceSqr :: (Positioned a, Positioned b) => a -> b -> Double
distanceSqr x y = (fst p1 - fst p2)**2 + (snd p1 - snd p2)**2
where
p1 = getPos x
p2 = getPos y

-- | Distance between a position and a bounding box
distanceBB :: (Positioned a, HasBoundingBox b) => a -> b -> Double
distanceBB p bb = sqrt \$ distanceBBSqr p bb

-- | Squared distance between a position and a bounding box
--   Faster than calculating the distance. Can be used to e.g. compare distances cheaply
distanceBBSqr :: (Positioned a, HasBoundingBox b) => a -> b -> Double
distanceBBSqr p bb = dx * dx + dy * dy
where
dx = max 0 \$ abs (xP - xBB) - w / 2.0
dy = max 0 \$ abs (yP - yBB) - h / 2.0
xP = getX p
yP = getY p
xBB = fst \$ centerBB \$ getBB bb
yBB = snd \$ centerBB \$ getBB bb
(w, h) = sizeBB \$ getBB bb

-- | Calculate the direction vector between two positions
direction :: (Positioned a, Positioned b) => a -> b -> RealPosition
direction x y = (newX, newY)
where
newX = ((fst p2) - (fst p1)) / l
newY = ((snd p2) - (snd p1)) / l
l = distance x y
p1 = getPos x
p2 = getPos y

-- | Find the closest in [b] to a
closest :: (Positioned a, Positioned b) => a -> [b] -> Maybe b
closest a bs = minimumByMay (  \ x y -> compare (distanceSqr a x) (distanceSqr a y)  ) bs

-- | Find the furthest in [b] to a
furthest :: (Positioned a, Positioned b) => a -> [b] -> Maybe b
furthest a bs = maximumByMay (  \ x y -> compare (distanceSqr a x) (distanceSqr a y)  ) bs

-- | Given a position and projectile speed of a gun / turret and an object defined by its current position and velocity
--   Calculates the position where both will intercept. (useful for pre-aiming)
interceptionPos :: (RealPosition, Double) -> (RealPosition, Velocity) -> RealPosition
interceptionPos (p1, v) (p2, v2) = (newX, newY)
where
tx = (fst p2) - (fst p1)
ty = (snd p2) - (snd p1)
tvx = fst v2
tvy = snd v2

a = tvx*tvx + tvy*tvy - v*v :: Double
b = 2 * (tvx * tx + tvy * ty) :: Double
c = tx*tx + ty*ty :: Double

ts = quadraticEquation a b c
t0 = fst ts
t1 = snd ts
temp = min t0 t1
t | temp > 0 = temp
| otherwise = max t0 t1

newX = (fst p2) + (fst v2) * t
newY = (snd p2) + (snd v2) * t

-- | Builder for a rigidbody
makeRB :: RealPosition -> Velocity -> Pixel -> Pixel -> RigidBody
makeRB center vel width height = RigidBody { rigidPos = center, rigidVel = vel, rigidBB = sizedBB center width height }

-- | Builder to get a BoundingBox from its center position and sizes
sizedBB :: RealPosition -> Pixel -> Pixel -> BoundingBox
sizedBB center width height = BoundingBox posMin posMax
where
posMin = (minX, minY)
posMax = (maxX, maxY)
minX = (fst center) - width / 2
minY = (snd center) - height / 2
maxX = (fst center) + width / 2
maxY = (snd center) + height / 2

-- | Calculates the size of a BoundingBox
sizeBB :: BoundingBox -> (Pixel, Pixel)
sizeBB bb = (width, height)
where
width  = (fst \$ bbMax bb) - (fst \$ bbMin bb)
height = (snd \$ bbMax bb) - (snd \$ bbMin bb)

-- | Calculates the center of a BoundingBox
centerBB :: BoundingBox -> RealPosition
centerBB bb = (newX, newY)
where
newX = (fst \$ bbMin bb) + (width / 2)
newY = (snd \$ bbMin bb) + (height / 2)
(width, height) = sizeBB bb

-- | Calculates the bounding box of multiple positions
bbFromList :: (Positioned a) => [a] -> BoundingBox
bbFromList []  = BBEmpty
bbFromList [_] = BBEmpty
bbFromList xs  = BoundingBox (minX, minY) (maxX, maxY)
where
minX = minimum \$ map getX xs
minY = minimum \$ map getY xs
maxX = maximum \$ map getX xs
maxY = maximum \$ map getY xs

-- | Merges two bounding boxes, creating a new one which wraps around the inputs
--   In case a nullBB is passed as one parameter, the other BoundingBox is returned
mergeBB :: BoundingBox -> BoundingBox -> BoundingBox
mergeBB BBEmpty bb2 = bb2
mergeBB bb1 BBEmpty = bb1
mergeBB bb1 bb2     = BoundingBox newMin newMax
where
newMin = mergeMin poss
newMax = mergeMax poss
poss = [bbMin bb1, bbMin bb2, bbMax bb1, bbMax bb2]

mergeMin :: [RealPosition]-> RealPosition
mergeMin poss = (x, y)
where
x = fst \$ minimumBy compareX poss
y = snd \$ minimumBy compareY poss

mergeMax :: [RealPosition] -> RealPosition
mergeMax poss = (x, y)
where
x = fst \$ maximumBy compareX poss
y = snd \$ maximumBy compareY poss

compareX a b = compare (fst a) (fst b)
compareY a b = compare (snd a) (snd b)

{- see above
tilePosToBB :: TilePosition -> BoundingBox
tilePosToBB pos = BoundingBox minPos maxPos
where
minPos = toRealPos \$ pos
maxPos = RealPosition maxX maxY
maxX = (fst minPos) + tileSize
maxY = (snd minPos) + tileSize
-}

---TODO sizedBB and makeBB are duplicates

-- | Builds a BoundingBox
makeBB :: RealPosition -> Pixel -> Pixel -> BoundingBox
makeBB center width height = BoundingBox newMin newMax
where
newMin = ((fst center - width / 2), (snd center - height / 2))
newMax = ((fst center + width / 2), (snd center + height / 2))

-- | Given a position, time and veilocty it calculates the position where the moving object would be
applyVelocity :: RealPosition -> Velocity -> Millisecond -> RealPosition
applyVelocity oldPos vel time =
(((fst oldPos) + (fromIntegral time) * (fst vel)),
((snd oldPos) + (fromIntegral time) * (snd vel)))
```