{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE CPP #-}

{- |

Use case:

* You've got a block of data you want to work
  with in C or C++ or some other foreign language
* It represents a 2D grid
* Before disposing of it, it would be convenient to treat
  it as a 'V.Vector' of vectors (perhaps for inspecting contents).
* Also, I just wanted to see what's involved in creating
  a Vector instance.

The resulting 'Grid' isn't an especially _good_ instance
of a Vector -- many operations on the 'top-level' vector
are disallowed --
and there's no mutable equivalent, but it seems to
work.

-}

module Data.Grid.Storable
  (
    -- * Vec-of-vec view of storable
    Grid

    -- ** plus helpful types
    --
    -- this is just to make types in repl less awful
  , InternalGrid

    -- * Accessors

    -- ** Length information
  , I.length

    -- ** Indexing
  , (I.!) , (I.!?), I.head, I.last
  , I.unsafeIndex, I.unsafeHead, I.unsafeLast

    -- ** Extracting subvectors (slicing)
  , I.slice, I.init, I.tail, I.take, I.drop

    -- * Construction

    -- ** Initialisation
  , fromVector

    -- * Destruction

  , toList

    -- ** Permutations
  , reverse

  -- * Raw pointers
  , unsafeToForeignPtr0
  )

  where

import Control.Monad
import Control.DeepSeq
import Control.Exception

#if !MIN_VERSION_base(4,11,0)
import Data.Monoid
#endif

#if !MIN_VERSION_base(4,8,0)
import Data.Functor( (<$>) )
#endif

import qualified Data.Vector as V
import qualified Data.Vector.Storable as VS
import qualified Data.Vector.Storable.Mutable as VSM

import Foreign ( ForeignPtr, Storable , advancePtr
                , touchForeignPtr , newForeignPtr_
                , withForeignPtr
                )

import System.IO.Unsafe

import qualified Data.Grid.Storable.Internal as I

type InternalGrid = I.Grid

-- | A vector-of-vector view of a block foreign contiguous data
type Grid a = InternalGrid a (VS.Vector a)

{-# ANN module ("HLint: ignore Eta reduce"::String) #-}
{-# ANN module ("HLint: ignore Use camelCase"::String) #-}

-- | Convert a storable 'VS.Vector' into vector-of-vectors view.
-- Does a "thaw" of the original 'VS.Vector', which results in a full copy,
-- being made, but has the advantage that
-- the source vector can safely be used again after.
--
-- width and height must be > 0 or user error thrown.
fromVector :: (Storable el) =>
      Int -> Int -> VS.Vector el -> Grid el
fromVector w h vec = unsafePerformIO $  do
    when (w < 1 || h < 1) $
      error $ "width and height must be > 0, were: " <> show (w,h)
    when ( (w * h) > VS.length vec ) $
      error $ "vec not big enough to hold width " <> show w
             <> " x height " <> show h
    mvec <- VS.thaw vec

    let (fptr, _flen) = VSM.unsafeToForeignPtr0 mvec
        offsets = take h [0, 0+w ..]

    rows <- withForeignPtr fptr $ \mptr ->
                  mapM (mk_row . advancePtr mptr) offsets


    let g = I.Grid (V.fromList rows) fptr
    return g
  where
    mk_row ptr = (`VS.unsafeFromForeignPtr0` w) <$> newForeignPtr_ ptr

-- | convert the 'Grid' into a list of (plain, not storable)
-- 'V.Vector's.
toList ::
  (Storable a, NFData a) => Grid a -> IO [V.Vector a]
toList grid = do
  let
      vecs = map VS.convert $ I.toList grid
      fptr = unsafeToForeignPtr0 grid
  vecs <- evaluate $ force vecs
  touchForeignPtr fptr
  return vecs


-- | O(1) Yield the underlying 'ForeignPtr'.
--
-- You can assume the pointer points directly to the data (no offset).
--
-- The data may not be modified through the 'ForeignPtr'.
unsafeToForeignPtr0 :: Grid el -> ForeignPtr el
unsafeToForeignPtr0 (I.Grid _ ptr) = ptr