-- | Dense primitive arrays where the lower index is zero (or the
-- equivalent of zero for newtypes and enumerations).
--
-- Actual @write@s to data structures use a more safe @write@ instead of
-- the unsafe @unsafeWrite@. Writes also tend to occur much less in DP
-- algorithms (say, N^2 writes for an N^3 time algorithm -- mostly reads
-- are being executed).
--
-- TODO consider if we want to force the lower index to be zero, or allow
-- non-zero lower indices. Will have to be considered together with the
-- @Index.Class@ module!
--
-- TODO while @Unboxed@ is, in princile, @Hashable@, we'd need the
-- corresponding @VU.Vector@ instances ...

module Data.PrimitiveArray.Dense where

import           Control.DeepSeq
import           Control.Exception (assert)
import           Control.Monad (liftM, forM_, zipWithM_)
import           Control.Monad.Primitive (PrimState)
import           Data.Aeson (ToJSON,FromJSON)
import           Data.Binary (Binary)
import           Data.Hashable (Hashable)
import           Data.Serialize (Serialize)
import           Data.Typeable (Typeable)
import           Data.Vector.Binary
import           Data.Vector.Generic.Mutable as GM hiding (length)
import           Data.Vector.Serialize
import           Data.Vector.Unboxed.Mutable (Unbox)
import           Debug.Trace
import           GHC.Generics (Generic)
import qualified Data.Vector as V hiding (forM_, length, zipWithM_)
import qualified Data.Vector.Generic as G
import qualified Data.Vector.Unboxed as VU hiding (forM_, length, zipWithM_)
import           Data.Data


import           Data.PrimitiveArray.Class
import           Data.PrimitiveArray.Index.Class



-- * Unboxed, multidimensional arrays.

data Unboxed sh e = Unboxed !(LimitType sh) !(VU.Vector e)

deriving instance (Eq      (LimitType sh), Eq e     , Unbox e)  Eq      (Unboxed sh e)
deriving instance (Generic (LimitType sh), Generic e, Unbox e)  Generic (Unboxed sh e)
deriving instance (Read    (LimitType sh), Read e   , Unbox e)  Read    (Unboxed sh e)
deriving instance (Show    (LimitType sh), Show e   , Unbox e)  Show    (Unboxed sh e)
deriving instance
  ( Data sh, Data (LimitType sh)
  , Data e, Unbox e
  )  Data    (Unboxed sh e)

instance (Binary    (LimitType sh), Binary    e, Unbox e, Generic (LimitType sh), Generic e) => Binary    (Unboxed sh e)
instance (Serialize (LimitType sh), Serialize e, Unbox e, Generic (LimitType sh), Generic e) => Serialize (Unboxed sh e)
instance (ToJSON    (LimitType sh), ToJSON    e, Unbox e, Generic (LimitType sh), Generic e) => ToJSON    (Unboxed sh e)
instance (FromJSON  (LimitType sh), FromJSON  e, Unbox e, Generic (LimitType sh), Generic e) => FromJSON  (Unboxed sh e)
instance (Hashable  (LimitType sh), Hashable  e, Hashable (VU.Vector e), Unbox e, Generic (LimitType sh), Generic e) => Hashable  (Unboxed sh e)

instance (NFData (LimitType sh)) => NFData (Unboxed sh e) where
  rnf (Unboxed h xs) = rnf h `seq` rnf xs
  {-# Inline rnf #-}

data instance MutArr m (Unboxed sh e) = MUnboxed !(LimitType sh) !(VU.MVector (PrimState m) e)
  deriving (Generic,Typeable)

instance (NFData (LimitType sh)) => NFData (MutArr m (Unboxed sh e)) where
  rnf (MUnboxed h xs) = rnf h `seq` rnf xs
  {-# Inline rnf #-}

instance
  ( Index sh
  , Unbox elm
#if ADPFUSION_DEBUGOUTPUT
  , Show sh, Show (LimitType sh), Show elm
#endif
  )  MPrimArrayOps Unboxed sh elm where
  upperBoundM (MUnboxed h _) = h
  fromListM h xs = do
    ma <- newM h
    let (MUnboxed _ mba) = ma
    zipWithM_ (\k x -> assert (length xs == size h) $ unsafeWrite mba k x) [0.. size h -1] xs
    return ma
  newM h = MUnboxed h `liftM` new (size h)
  newWithM h def = do
    ma <- newM h
    let (MUnboxed _ mba) = ma
    forM_ [0 .. size h -1] $ \k -> unsafeWrite mba k def
    return ma
  readM  (MUnboxed h mba) idx     = assert (inBounds h idx) $ unsafeRead  mba (linearIndex h idx)
  writeM (MUnboxed h mba) idx elm =
#if ADPFUSION_DEBUGOUTPUT
    (if inBounds h idx then id else traceShow ("writeM", h, idx, elm, size h, linearIndex h idx, inBounds h idx))
#endif
    assert (inBounds h idx) $ unsafeWrite mba (linearIndex h idx) elm
  {-# INLINE upperBoundM #-}
  {-# INLINE fromListM #-}
  {-# NoInline newM #-}
  {-# INLINE newWithM #-}
  {-# INLINE readM #-}
  {-# INLINE writeM #-}

instance (Index sh, Unbox elm) => PrimArrayOps Unboxed sh elm where
  upperBound (Unboxed h _) = h
  unsafeFreeze (MUnboxed h mba) = Unboxed h `liftM` G.unsafeFreeze mba
  unsafeThaw   (Unboxed  h ba) = MUnboxed h `liftM` G.unsafeThaw ba
  unsafeIndex  (Unboxed  h ba) idx = G.unsafeIndex ba (linearIndex h idx)
  transformShape tr (Unboxed h ba) = Unboxed (tr h) ba
  {-# INLINE upperBound #-}
  {-# INLINE unsafeFreeze #-}
  {-# INLINE unsafeThaw #-}
  {-# INLINE unsafeIndex #-}
  {-# INLINE transformShape #-}

instance (Index sh, Unbox e, Unbox e') => PrimArrayMap Unboxed sh e e' where
  map f (Unboxed h xs) = Unboxed h (VU.map f xs)
  {-# INLINE map #-}



-- * Boxed, multidimensional arrays.

data Boxed sh e = Boxed !(LimitType sh) !(V.Vector e)

deriving instance (Read    (LimitType sh), Read e)  Read (Boxed sh e)
deriving instance (Show    (LimitType sh), Show e)  Show (Boxed sh e)
deriving instance (Eq      (LimitType sh), Eq   e)  Eq   (Boxed sh e)
deriving instance (Generic (LimitType sh), Generic e)  Generic (Boxed sh e)
deriving instance
  ( Data sh, Data (LimitType sh)
  , Data e
  )  Data    (Boxed sh e)


instance (Binary    (LimitType sh), Binary    e, Unbox e, Generic (LimitType sh), Generic e) => Binary    (Boxed sh e)
instance (Serialize (LimitType sh), Serialize e, Unbox e, Generic (LimitType sh), Generic e) => Serialize (Boxed sh e)
instance (ToJSON    (LimitType sh), ToJSON    e, Unbox e, Generic (LimitType sh), Generic e) => ToJSON    (Boxed sh e)
instance (FromJSON  (LimitType sh), FromJSON  e, Unbox e, Generic (LimitType sh), Generic e) => FromJSON  (Boxed sh e)
instance (Hashable  (LimitType sh), Hashable  e, Hashable (V.Vector e), Unbox e, Generic (LimitType sh), Generic e) => Hashable  (Boxed sh e)

instance (NFData (LimitType sh), NFData e) => NFData (Boxed sh e) where
  rnf (Boxed h xs) = rnf h `seq` rnf xs
  {-# Inline rnf #-}

data instance MutArr m (Boxed sh e) = MBoxed !(LimitType sh) !(V.MVector (PrimState m) e)
  deriving (Generic,Typeable)

instance (NFData (LimitType sh)) => NFData (MutArr m (Boxed sh e)) where
  rnf (MBoxed h xs) = rnf h -- no rnf for the data !
  {-# Inline rnf #-}

instance (Index sh) => MPrimArrayOps Boxed sh elm where
  upperBoundM (MBoxed h _) = h
  fromListM h xs = do
    ma <- newM h
    let (MBoxed _ mba) = ma
    zipWithM_ (\k x -> assert (length xs == size h) $ unsafeWrite mba k x) [0 .. size h - 1] xs
    return ma
  newM h =
    MBoxed h `liftM` new (size h)
  newWithM h def = do
    ma <- newM h
    let (MBoxed _ mba) = ma
    forM_ [0 .. size h -1] $ \k -> unsafeWrite mba k def
    return ma
  readM  (MBoxed h mba) idx     = assert (inBounds h idx) $ GM.unsafeRead  mba (linearIndex h idx)
  writeM (MBoxed h mba) idx elm = assert (inBounds h idx) $ GM.unsafeWrite mba (linearIndex h idx) elm
  {-# INLINE upperBoundM #-}
  {-# INLINE fromListM #-}
  {-# NoInline newM #-}
  {-# INLINE newWithM #-}
  {-# INLINE readM #-}
  {-# INLINE writeM #-}

instance (Index sh) => PrimArrayOps Boxed sh elm where
  upperBound (Boxed h _) = h
  unsafeFreeze (MBoxed h mba) = Boxed h `liftM` G.unsafeFreeze mba
  unsafeThaw   (Boxed h ba) = MBoxed h `liftM` G.unsafeThaw ba
  unsafeIndex (Boxed h ba) idx = assert (inBounds h idx) $ G.unsafeIndex ba (linearIndex h idx)
  transformShape tr (Boxed h ba) = Boxed (tr h) ba
  {-# INLINE upperBound #-}
  {-# INLINE unsafeFreeze #-}
  {-# INLINE unsafeThaw #-}
  {-# INLINE unsafeIndex #-}
  {-# INLINE transformShape #-}

instance (Index sh) => PrimArrayMap Boxed sh e e' where
  map f (Boxed h xs) = Boxed h (V.map f xs)
  {-# INLINE map #-}



{-
 -
 - This stuff tells us how to write efficient generics on large data
 - constructors like the Turner and Vienna ctors.
 -

import qualified Data.Generics.TH as T

data Unboxed sh e = Unboxed !sh !(VU.Vector e)
  deriving (Show,Eq,Ord)

data X e = X (Unboxed DIM1 e) (Unboxed DIM1 e)
  deriving (Show,Eq,Ord)

x :: X Int
x = X z z where z = (Unboxed (Z:.10) (VU.fromList [ 0 .. 10] ))

pot :: X Int -> X Double
pot = $( T.thmapT (T.mkTs ['f]) [t| X Int |] ) where
  f :: Unboxed DIM1 Int -> Unboxed DIM1 Double
  f (Unboxed sh xs) = Unboxed sh (VU.map fromIntegral xs)

-}