{-# OPTIONS_GHC -w #-}
{-# LANGUAGE BangPatterns          #-}
{-# LANGUAGE ConstraintKinds       #-}
{-# LANGUAGE FlexibleContexts      #-}
{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NamedFieldPuns        #-}
{-# LANGUAGE TypeFamilies          #-}
module Data.Vector.Dynamic
( DynamicVector
, newDyn
, newDyn'
, readDyn
, writeDyn
, pushDyn
, popDyn
, shiftDyn
, unshiftDyn
, sizeDyn
, toListDyn
) where
import           Control.Imperative.Vector.Base (VectorElem, VectorEntity)
import           Control.Monad
import           Control.Monad.Primitive        (PrimMonad, PrimState)
import           Data.Bits
import           Data.Int
import           Data.Primitive.MutVar
import qualified Data.Vector.Generic.Mutable    as GMV
import qualified Data.Vector.Mutable            as MV
import qualified Data.Vector.Unboxed            as UV
import           Data.Word

data DynamicVector m a = Dynamic
  { logSizeVar :: MutVar (PrimState m) Int
  , frontVar   :: MutVar (PrimState m) Int
  , rearVar    :: MutVar (PrimState m) Int
  , vecVar     :: MutVar (PrimState m) (VectorEntity a (PrimState m) a)
  }

toPow2 :: Int -> Int
toPow2 n
  | n <= 0 = 1
  | otherwise = go (n-1) 1
    where
      go 0 !c = c
      go n !c = go (shiftR n 1) (shiftL c 1)
{-# INLINE toPow2 #-}

newDyn :: (PrimMonad m, VectorElem a) => Int -> m (DynamicVector m a)
newDyn logSize = liftM4 Dynamic
  (newMutVar logSize)
  (newMutVar 0)
  (newMutVar (max 0 (logSize-1)))
  (GMV.unsafeNew (toPow2 logSize) >>= newMutVar)
{-# INLINE newDyn #-}

newDyn' :: (PrimMonad m, VectorElem a) => Int -> a -> m (DynamicVector m a)
newDyn' logSize x = do
  d@(Dynamic {vecVar}) <- newDyn logSize
  v <- readMutVar vecVar
  w <- GMV.replicate logSize x
  unsafeMemCopy 0 v 0 (max 0 logSize) w
  return d
{-# INLINE newDyn' #-}

unsafeMemCopy
  :: (PrimMonad m, GMV.MVector v a)
  => Int -- dst offset
  -> v (PrimState m) a -- dst
  -> Int -- src offset
  -> Int -- length
  -> v (PrimState m) a -- src
  -> m ()
unsafeMemCopy i dst j n src = do
  let sdst = GMV.unsafeSlice i n dst
      ssrc = GMV.unsafeSlice j n src
  GMV.unsafeCopy sdst ssrc
{-# INLINE unsafeMemCopy #-}

readDyn :: (PrimMonad m, VectorElem a) => DynamicVector m a -> Int -> m a
readDyn d@(Dynamic {logSizeVar, frontVar, rearVar, vecVar}) logIndex = do
  logSize  <- readMutVar logSizeVar
  when (logSize <= logIndex || logIndex < 0) $
    error $ "index out of bounds " ++ show (logIndex, logSize)
  phyIndex <- log2phy d logIndex
  vec <- readMutVar vecVar
  GMV.unsafeRead vec phyIndex
{-# INLINE readDyn #-}

log2phy :: (PrimMonad m, VectorElem a) => DynamicVector m a -> Int -> m Int
log2phy (Dynamic {frontVar, vecVar}) i = do
  front <- readMutVar frontVar
  vec   <- readMutVar vecVar
  let realCap = GMV.length vec
  return $ (front + i) .&. (realCap - 1)
{-# INLINE log2phy #-}

writeDyn :: (PrimMonad m, VectorElem a) => DynamicVector m a -> Int -> a -> m ()
writeDyn d@(Dynamic {logSizeVar, rearVar, vecVar}) logIndex x = do
  logSize <- readMutVar logSizeVar
  when (logIndex < 0) $
    error $ "index out of bounds " ++ show (logIndex, logSize)
  when (logSize <= logIndex) $ do
    resizeDyn d (logIndex + 1)
    log2phy d logIndex >>= writeMutVar rearVar
  phyIndex <- log2phy d logIndex
  vec <- readMutVar vecVar
  GMV.unsafeWrite vec phyIndex x
{-# INLINE writeDyn #-}

resizeDyn :: (PrimMonad m, VectorElem a) => DynamicVector m a -> Int -> m ()
resizeDyn (Dynamic {logSizeVar, frontVar, rearVar, vecVar}) newLogSize = do
  vec <- readMutVar vecVar
  let realCap = GMV.length vec
  when (realCap < newLogSize) $ do
    let newRealCap = until (newLogSize <=) (*2) realCap
        diff = newRealCap - realCap
    front <- readMutVar frontVar
    rear <- readMutVar rearVar
    newVec <- if front > rear
      then do
        let n = realCap - front
        nv <- GMV.unsafeNew newRealCap
        unsafeMemCopy 0 nv 0 (rear+1) vec
        unsafeMemCopy (front + diff) nv front n vec
        writeMutVar frontVar (front + diff)
        return nv
      else GMV.unsafeGrow vec diff
    writeMutVar vecVar newVec
  writeMutVar logSizeVar newLogSize
{-# INLINE resizeDyn #-}

pushDyn :: (PrimMonad m, VectorElem a) => DynamicVector m a -> a -> m ()
pushDyn d@(Dynamic {logSizeVar, rearVar}) x = do
  logSize <- readMutVar logSizeVar
  writeDyn d logSize x
{-# INLINE pushDyn #-}

popDyn :: (PrimMonad m, VectorElem a) => DynamicVector m a -> m a
popDyn d@(Dynamic {logSizeVar, frontVar, rearVar}) = do
  logSize <- readMutVar logSizeVar
  when (logSize < 1) $ error "Couldn't pop on an empty vector"
  x <- readDyn d (logSize-1)
  when (logSize > 1) $
    log2phy d (logSize-2) >>= writeMutVar rearVar
  modifyMutVar' logSizeVar pred
  return x
{-# INLINE popDyn #-}

shiftDyn :: (PrimMonad m, VectorElem a) => DynamicVector m a -> m a
shiftDyn d@(Dynamic {logSizeVar, frontVar, rearVar, vecVar}) = do
  logSize <- readMutVar logSizeVar
  when (logSize < 1) $ error "Couldn't shift on an empty vector"
  x <- readDyn d 0
  when (logSize > 1) $
    log2phy d 1 >>= writeMutVar frontVar
  modifyMutVar' logSizeVar pred
  return x
{-# INLINE shiftDyn #-}

unshiftDyn :: (PrimMonad m, VectorElem a) => DynamicVector m a -> a -> m ()
unshiftDyn d@(Dynamic {logSizeVar, frontVar, rearVar, vecVar}) x = do
  logSize <- readMutVar logSizeVar
  realCap <- liftM GMV.length $ readMutVar vecVar
  if logSize == realCap
    then resizeDyn d (logSize+1)
    else modifyMutVar' logSizeVar succ
  phyNewHead <- log2phy d (-1)
  vec <- readMutVar vecVar
  GMV.unsafeWrite vec phyNewHead x
  writeMutVar frontVar phyNewHead
  when (logSize==0) $ writeMutVar rearVar phyNewHead
{-# INLINE unshiftDyn #-}

toListDyn :: (PrimMonad m, VectorElem a) => DynamicVector m a -> m [a]
toListDyn (Dynamic {logSizeVar, frontVar, rearVar, vecVar}) = do
  logSize <- readMutVar logSizeVar
  if logSize == 0
    then return []
    else do
      front <- readMutVar frontVar
      rear <- readMutVar rearVar
      vec  <- readMutVar vecVar
      let realCap = GMV.length vec
          indicies = if front <= rear then [front..rear] else [front..realCap-1] ++ [0..rear]
      forM indicies $ GMV.unsafeRead vec
{-# INLINE toListDyn #-}

sizeDyn :: PrimMonad m => DynamicVector m a -> m Int
sizeDyn (Dynamic {logSizeVar}) = readMutVar logSizeVar
{-# INLINE sizeDyn #-}