{-# LANGUAGE BangPatterns #-}
module Data.Array.Repa.Internals.Forcing
	( toVector
	, toList
	, force, force2)
where
import Data.Array.Repa.Internals.EvalChunked
import Data.Array.Repa.Internals.EvalCursored
import Data.Array.Repa.Internals.Elt
import Data.Array.Repa.Internals.Base
import Data.Array.Repa.Index
import Data.Array.Repa.Shape			as S
import qualified Data.Vector.Unboxed		as V
import qualified Data.Vector.Unboxed.Mutable	as VM
import Data.Vector.Unboxed			(Vector)
import System.IO.Unsafe

stage	= "Data.Array.Repa.Internals.Forcing"


-- Conversions that also force the array ----------------------------------------------------------
-- | Convert an array to an unboxed `Data.Vector`, forcing it if required.
--	The elements come out in row-major order.
toVector
	:: (Shape sh, Elt a)
	=> Array sh a
	-> Vector a
{-# INLINE toVector #-}
toVector arr
 = case force arr of
	Array _ [Region _ (GenManifest vec)]	-> vec
	_	-> error $ stage ++ ".toVector: force failed"


-- | Convert an array to a list, forcing it if required.
toList 	:: (Shape sh, Elt a)
	=> Array sh a
	-> [a]

{-# INLINE toList #-}
toList arr
 = V.toList $ toVector arr


-- Forcing ----------------------------------------------------------------------------------------
-- | Force an array, so that it becomes `Manifest`.
--   The array is split into linear chunks and each chunk evaluated in parallel.
force	:: (Shape sh, Elt a)
	=> Array sh a -> Array sh a

{-# INLINE [2] force #-}
force arr
 = unsafePerformIO
 $ do	(sh, vec)	<- forceIO arr
	return $ sh `seq` vec `seq`
		 Array sh [Region RangeAll (GenManifest vec)]

 where	forceIO arr'
	 = case arr' of
		-- Don't force an already forced array.
		Array sh [Region RangeAll (GenManifest vec)]
		 -> 	return (sh, vec)

		Array sh _
		 -> do	mvec	<- VM.unsafeNew (S.size sh)
			fillChunkedP mvec (\ix -> arr' `unsafeIndex` fromIndex sh ix)
			vec	<- V.unsafeFreeze mvec
			return	(sh, vec)


-- | Force an array, so that it becomes `Manifest`.
--   This forcing function is specialised for DIM2 arrays, and does blockwise filling.
force2	:: Elt a => Array DIM2 a -> Array DIM2 a
{-# INLINE [2] force2 #-}
force2 arr
 = unsafePerformIO
 $ do	(sh, vec)	<- forceIO2 arr
	return $ sh `seq` vec `seq`
		 Array sh [Region RangeAll (GenManifest vec)]

 where	forceIO2 arr'
 	 = arr' `deepSeqArray`
	   case arr' of
		-- Don't force an already forced array.
		Array sh [Region RangeAll (GenManifest vec)]
	 	 -> 	return (sh, vec)

		-- Create a vector to hold the new array and load in the regions.
		-- NOTE We must specialise this for the common case of two regions to enable
		--      fusion for them. If we just have the next case (arbitrary region list)
		--      the worker won't fuse with the filling / evaluation code.
		Array sh [r1]
		 -> do	mvec	<- VM.new (S.size sh)
			fillRegion2P mvec sh r1
			vec	<- V.unsafeFreeze mvec
			return (sh, vec)

		Array sh [r1, r2]
	 	 -> do	mvec	<- VM.new (S.size sh)
			fillRegion2P mvec sh r1
			fillRegion2P mvec sh r2
			vec	<- V.unsafeFreeze mvec
			return (sh, vec)

		-- Create a vector to hold the new array and load in the regions.
		Array sh regions
	 	 -> do	mvec	<- VM.new (S.size sh)
			mapM_ (fillRegion2P mvec sh) regions
			vec	<- V.unsafeFreeze mvec
			return (sh, vec)


-- FillRegion2P -----------------------------------------------------------------------------------
-- | Fill an array region into a vector.
--   This is specialised for DIM2 regions.
--   The region is evaluated in parallel in a blockwise manner, where each block is
--   evaluated independently and in a separate thread. For delayed or cursored regions
--   access their source elements from the local neighbourhood, this specialised version
--   should given better cache performance than plain `fillRegionP`.
--
fillRegion2P
	:: Elt a
	=> VM.IOVector a	-- ^ Vector to write elements into.
	-> DIM2			-- ^ Extent of entire array.
	-> Region DIM2 a	-- ^ Region to fill.
	-> IO ()

{-# INLINE [1] fillRegion2P #-}
fillRegion2P mvec sh@(_ :. height :. width) (Region range gen)
 = mvec `seq` height `seq` width `seq`
   case range of
	RangeAll
	 -> fillRect2 mvec sh gen
		(Rect 	(Z :. 0          :. 0)
			(Z :. height - 1 :. width - 1))

	RangeRects _ [rect]
	 -> fillRect2 mvec sh gen rect

	-- Specialise for the common case of 4 rectangles so we get fusion.
	-- The following case with mapM_ doesn't fuse because mapM_ isn't completely unrolled.
	RangeRects _ [r1, r2, r3, r4]
	 -> do	fillRect2 mvec sh gen r1
		fillRect2 mvec sh gen r2
		fillRect2 mvec sh gen r3
		fillRect2 mvec sh gen r4

	RangeRects _ rects
	 -> mapM_ (fillRect2 mvec sh gen) rects


-- | Fill a rectangle in a vector.
fillRect2
	:: Elt a
	=> VM.IOVector a	-- ^ Vector to write elements into.
	-> DIM2 		-- ^ Extent of entire array.
	-> Generator DIM2 a	-- ^ Generator for array elements.
	-> Rect DIM2		-- ^ Rectangle to fill.
	-> IO ()

{-# INLINE fillRect2 #-}
fillRect2 mvec (_ :. _ :. width) gen (Rect (Z :. y0 :. x0) (Z :. y1 :. x1))
 = mvec `seq` width `seq` y0 `seq` x0 `seq` y1 `seq` x1 `seq`
   case gen of
	GenManifest{}
	 -> error "fillRegion2P: GenManifest, copy elements."

	-- Cursor based arrays.
	GenCursor makeCursor shiftCursor loadElem
         -> fillCursoredBlock2P mvec
		makeCursor shiftCursor loadElem
		width x0 y0 x1 y1