-- |
-- Module      : Cartesian.Internal.Lenses
-- Description :
-- Copyright   : (c) Jonatan H Sundqvist, 2015
-- License     : MIT
-- Maintainer  : Jonatan H Sundqvist
-- Stability   : experimental|stable
-- Portability : POSIX (not sure)
--

-- Created October 31 2015

-- TODO | -
--        -

-- SPEC | -
--        -



--------------------------------------------------------------------------------------------------------------------------------------------
-- GHC Pragmas
--------------------------------------------------------------------------------------------------------------------------------------------
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TemplateHaskell       #-}
{-# LANGUAGE RankNTypes            #-}



--------------------------------------------------------------------------------------------------------------------------------------------
-- API
--------------------------------------------------------------------------------------------------------------------------------------------
module Cartesian.Internal.Lenses where



--------------------------------------------------------------------------------------------------------------------------------------------
-- We'll need these
--------------------------------------------------------------------------------------------------------------------------------------------
import Data.List     (isSuffixOf)
import Control.Monad (mfilter)
import Control.Lens

import Language.Haskell.TH

import Cartesian.Internal.Types
import Cartesian.Internal.Utils



--------------------------------------------------------------------------------------------------------------------------------------------
-- Lenses
--------------------------------------------------------------------------------------------------------------------------------------------

-- Vector ----------------------------------------------------------------------------------------------------------------------------------

-- | Focus on the X component
x :: HasX v f => Lens v v f f
x = lens getX setX


-- | Focus on the Y component
y :: HasY v f => Lens v v f f
y = lens getY setY


-- | Focus on the Z component
z :: HasZ v f => Lens v v f f
z = lens getZ setZ

-- BoundingBox -----------------------------------------------------------------------------------------------------------------------------

-- TODO: Relative lenses (eg. padding)
-- TODO: Validate (eg. make sure that left < right)
-- TODO: Type-changing lenses (?)


-- | Ugh...
makeLensesWith (lensRules & lensField .~ (\_ _ name -> [TopName (mkName $ dropSuffix "Of" (nameBase name))])) (''BoundingBox) -- TODO: 'Of'


-- |
-- TODO: Rename (?)
offset :: (Fractional f) => (Getter v f) -> (f -> f -> f) -> BoundingBox v -> f
offset axis towards box = towards (box^.centre.axis) (0.5 * box^.size.axis)

--------------------------------------------------------------------------------------------------------------------------------------------

-- |
-- pad :: f -> (Getter v f) -> f -> BoundingBox v
-- pad by axis direction = _


-- | Moves one side of a BoundingBox along the given 'axis' so that its new position is at 'to'. The 'towards' parameter is expected to be
--   either (-) and (+), indicating which side along the axis we're dealing with.
-- TODO: Turn this into a lens function (?)
-- TODO: Polish description
-- TODO: Loosen constraint on f
side :: (Fractional f) => Lens v v f f -> (f -> f -> f) -> Lens (BoundingBox v) (BoundingBox v) f f
side axis towards = lens get set
  where
    get = offset axis towards
    set box to = let newsize = abs (to - towards centre' (-(box^.size.axis)*0.5))
                     centre' = box^.centre.axis --
                 in BoundingBox { sizeOf=(box^.size) & axis .~ newsize, centreOf=(box^.centre) & axis .~ (to `towards` negate (newsize*0.5)) } -- TODO: Refactor. And then refactor some more.

--------------------------------------------------------------------------------------------------------------------------------------------

width :: (HasX v f) => Lens (BoundingBox v) (BoundingBox v) f f
width = size.x


height :: (HasY v f) => Lens (BoundingBox v) (BoundingBox v) f f
height = size.y


depth :: (HasZ v f) => Lens (BoundingBox v) (BoundingBox v) f f
depth = size.z

-- So much boilerplate it makes me cry -----------------------------------------------------------------------------------------------------

-- type SideLens = (Fractional f, HasX v f) => Lens (BoundingBox v) (BoundingBox v) f f

left :: (Fractional f, HasX v f) => Lens (BoundingBox v) (BoundingBox v) f f
left = side x (-)


right :: (Fractional f, HasX v f) => Lens (BoundingBox v) (BoundingBox v) f f
right = side x (+)


bottom :: (Fractional f, HasY v f) => Lens (BoundingBox v) (BoundingBox v) f f
bottom = side y (-)


top :: (Fractional f, HasY v f) => Lens (BoundingBox v) (BoundingBox v) f f
top = side y (+)


front :: (Fractional f, HasZ v f) => Lens (BoundingBox v) (BoundingBox v) f f
front = side z (-)


back :: (Fractional f, HasZ v f) => Lens (BoundingBox v) (BoundingBox v) f f
back = side z (+)