----------------------------------------------------------------------------
-- |
-- Module      :  FastTags.LensBlaze
-- Copyright   :  (c) Sergey Vinokurov 2019
--
-- Minimal implementation of lenses. The aim is to get lenses
-- that allow to work with parts of an Int as another Int.
--
-- E.g. to be able to treat upper 32 bits and lower 32 bits of an
-- integer as two different integers, read them independently and set
-- back.
--
-- This way values that have limited range will not occupy whole
-- pointer when stored in a struct, but rather will all be tightly
-- packed in a single integer "store" field that can be {-# UNPACK #-}'ed.
--
-- E.g. suppose we define 2d points that will be drawn on a computer
-- screen. We could start with
--
-- > data Point = Point
-- >    { getX :: {-# UNPACK #-} !Int
-- >    , getY :: {-# UNPACK #-} !Int
-- >    }
--
-- Now, this is not too bad but but observe that screens don't
-- typically require 64 bits of precision - 32 would be enough! If
-- only we could pack two Int32's into single Int64, that would occupy
-- twice as less space. But we don't want to have bit masks and shifts all
-- over the place in client code - that's error-prone. This is where this
-- module comes in. Instead of the above, we can do:
--
-- > newtype Point = Point { unPoint :: Int64 }
-- >
-- > pointL :: Lens Point Int64
-- > pointL = lens unPoint (\x _ -> Point x)
-- >
-- > xCoordL, yCoordL :: Lens Point Int32
-- > xCoordL = pointL . int32L 0
-- > yCoordL = pointL . int32L 32
--
-- Now, all the accesses to x and y coordinate have to go through lenses and
-- we lose record update syntax. But that's a manageable price and we get 2x
-- savings on memory traffic.
----------------------------------------------------------------------------

{-# LANGUAGE BangPatterns        #-}
{-# LANGUAGE RankNTypes          #-}
{-# LANGUAGE ScopedTypeVariables #-}

module FastTags.LensBlaze
    ( Lens
    , Lens'
    , lens
    , view
    , over
    , set
    , int16L
    , int32L
    , intL
    ) where

import Control.Applicative

import Data.Bits
import Data.Functor.Identity
import Data.Int

type Lens' s a = Lens s s a a
type Lens s t a b = forall f. Functor f => (a -> f b) -> (s -> f t)

{-# INLINE lens #-}
lens :: (s -> a) -> (b -> s -> t) -> Lens s t a b
lens access write = \f s -> (\b -> write b s) <$> f (access s)

{-# INLINE view #-}
view :: Lens s t a b -> s -> a
view l = getConst . l Const

{-# INLINE set #-}
set :: Lens s t a b -> b -> s -> t
set l = over l . const

{-# INLINE over #-}
over :: Lens s t a b -> (a -> b) -> s -> t
over l f = runIdentity . l (Identity . f)

{-# INLINE int16L #-}
int16L :: (Bits b, Integral b) => Int -> Lens' b Int16
int16L offset = intL offset 0xffff

{-# INLINE int32L #-}
int32L :: (Bits b, Integral b) => Int -> Lens' b Int32
int32L offset = intL offset 0xffffffff

{-# INLINE intL #-}
intL :: forall a b. (Integral a, Bits b, Integral b) => Int -> b -> Lens' b a
intL !offset !mask = \f x ->
    (\x' -> (fromIntegral x' `unsafeShiftL` offset) .|. (x .&. reverseMask)) <$>
    f (fromIntegral ((x `unsafeShiftR` offset) .&. mask :: b))
    where
    reverseMask :: b
    !reverseMask = complement $ mask `unsafeShiftL` offset