{-# LANGUAGE MagicHash #-}
--   This is specialised for stencils up to 7x7.
--   Due to limitations in the GHC optimiser, using larger stencils doesn't
--   work, and will yield `error` at runtime. We can probably increase the
--   limit if required -- just ask.
--
--   The focus of the stencil is in the center of the 7x7 tile, which has
--   coordinates (0, 0). All coefficients in the stencil must fit in the tile,
--   so they can be given X,Y coordinates up to +/- 3 positions.
--   The stencil can be any shape, and need not be symmetric -- provided it
--   fits in the 7x7 tile.
--
module Data.Array.Repa.Stencil.Dim2
	( -- * Stencil creation
	  makeStencil2, stencil2

	  -- * Stencil operators
	, PC5, mapStencil2, forStencil2)
where
import Data.Array.Repa.Base
import Data.Array.Repa.Index
import Data.Array.Repa.Shape
import Data.Array.Repa.Repr.Delayed
import Data.Array.Repa.Repr.Cursored
import Data.Array.Repa.Repr.Partitioned
import Data.Array.Repa.Repr.HintSmall
import Data.Array.Repa.Repr.Undefined
import Data.Array.Repa.Stencil.Base
import Data.Array.Repa.Stencil.Template
import Data.Array.Repa.Stencil.Partition
import GHC.Exts

-- | A index into the flat array.
--   Should be abstract outside the stencil modules.
data Cursor
	= Cursor Int

type PC5 = P C (P (S D) (P (S D) (P (S D) (P (S D) X))))


-- Wrappers -------------------------------------------------------------------
-- | Like `mapStencil2` but with the parameters flipped.
forStencil2
        :: Source r a
        => Boundary a
	-> Array  r DIM2 a
	-> Stencil  DIM2 a
	-> Array PC5 DIM2 a

{-# INLINE forStencil2 #-}
forStencil2 boundary arr stencil
	= mapStencil2 boundary stencil arr


-------------------------------------------------------------------------------
-- | Apply a stencil to every element of a 2D array.
mapStencil2
        :: Source r a
        => Boundary a		-- ^ How to handle the boundary of the array.
	-> Stencil DIM2 a	-- ^ Stencil to apply.
	-> Array r DIM2 a		-- ^ Array to apply stencil to.
	-> Array PC5 DIM2 a

{-# INLINE mapStencil2 #-}
mapStencil2 boundary stencil@(StencilStatic sExtent _zero _load) arr
 = let	sh                       = extent arr
        (_ :. aHeight :. aWidth) = sh
	(_ :. sHeight :. sWidth) = sExtent

	sHeight2	= sHeight `div` 2
	sWidth2		= sWidth  `div` 2

        -- Partition the array into the internal and border regions.
        ![ Region    inX    inY    inW    inH
         , Region  westX  westY  westW  westH
         , Region  eastX  eastY  eastW  eastH
         , Region northX northY northW northH 
         , Region southX southY southW southH ] 
           = partitionForStencil 
                (Size   aWidth   aHeight) 
                (Size   sWidth   sHeight)
                (Offset sWidth2  sHeight2)

	{-# INLINE inInternal #-}
	inInternal (Z :. y :. x)
		=  x >= inX && x < (inX + inW)
		&& y >= inY && y < (inY + inH)

	{-# INLINE inBorder #-}
	inBorder       = not . inInternal

	-- Cursor functions ----------------
	{-# INLINE makec #-}
	makec (Z :. y :. x)
	 = Cursor (x + y * aWidth)

	{-# INLINE shiftc #-}
	shiftc ix (Cursor off)
	 = Cursor
	 $ case ix of
		Z :. y :. x	-> off + y * aWidth + x

        {-# INLINE arrInternal #-}
        arrInternal     = makeCursored (extent arr) makec shiftc getInner' 

        {-# INLINE getInner' #-}
        getInner' cur   = unsafeAppStencilCursor2 shiftc stencil arr cur

        {-# INLINE arrBorder #-}
        arrBorder       = ASmall (fromFunction (extent arr) getBorder')

        {-# INLINE getBorder' #-}
        getBorder' ix
         = case boundary of
                BoundFixed c    -> c
                BoundConst c    -> unsafeAppStencilCursor2_const addDim stencil c arr ix
                BoundClamp      -> unsafeAppStencilCursor2_clamp addDim stencil arr ix
   in
    --  internal region
        APart sh (Range (Z :.    inY :.    inX) (Z :.    inH :.    inW) inInternal) arrInternal

    --  border regions
    $   APart sh (Range (Z :.  westY :.  westX) (Z :.  westH :.  westW) inBorder)   arrBorder
    $   APart sh (Range (Z :.  eastY :.  eastX) (Z :.  eastH :.  eastW) inBorder)   arrBorder
    $   APart sh (Range (Z :. northY :. northX) (Z :. northH :. northW) inBorder)   arrBorder
    $   APart sh (Range (Z :. southY :. southX) (Z :. southH :. southW) inBorder)   arrBorder
    $   AUndefined sh


unsafeAppStencilCursor2
	:: Source r a
	=> (DIM2 -> Cursor -> Cursor)
	-> Stencil DIM2 a
	-> Array r DIM2 a
	-> Cursor
	-> a

{-# INLINE unsafeAppStencilCursor2 #-}
unsafeAppStencilCursor2 shift
        (StencilStatic sExtent zero loads)
	arr cur0

	| _ :. sHeight :. sWidth	<- sExtent
	, sHeight <= 7, sWidth <= 7
	= let
		-- Get data from the manifest array.
		{-# INLINE getData #-}
		getData (Cursor cur) = arr `unsafeLinearIndex` cur

		-- Build a function to pass data from the array to our stencil.
		{-# INLINE oload #-}
		oload oy ox
		 = let	!cur' = shift (Z :. oy :. ox) cur0
		   in	loads (Z :. oy :. ox) (getData cur')

	   in	template7x7 oload zero

        | otherwise
        = error $ unlines 
                [ "mapStencil2: Your stencil is too big for this method."
                , " It must fit within a 7x7 tile to be compiled statically." ]


-- | Like above, but treat elements outside the array has having a constant value.
unsafeAppStencilCursor2_const
        :: forall r a
        .  Source r a
        => (DIM2 -> DIM2 -> DIM2)
        -> Stencil DIM2 a
        -> a
        -> Array r DIM2 a
        -> DIM2
        -> a

{-# INLINE unsafeAppStencilCursor2_const #-}
unsafeAppStencilCursor2_const shift
           (StencilStatic sExtent zero loads)
           fixed arr cur

        | _ :. sHeight      :. sWidth       <- sExtent
        , _ :. (I# aHeight) :. (I# aWidth)  <- extent arr
        , sHeight <= 7, sWidth <= 7
        = let
                -- Get data from the manifest array.
                {-# INLINE getData #-}
                getData :: DIM2 -> a
                getData (Z :. (I# y) :. (I# x))
                 = getData' x y

                {-# NOINLINE getData' #-}
                getData' :: Int# -> Int# -> a
                getData' !x !y
                 |   x <# 0# || x >=# aWidth
                  || y <# 0# || y >=# aHeight
                 = fixed

                 | otherwise
                 = arr `unsafeIndex` (Z :. (I# y) :.  (I# x))

                -- Build a function to pass data from the array to our stencil.
                {-# INLINE oload #-}
                oload oy ox
                 = let  !cur' = shift (Z :. oy :. ox) cur
                   in   loads (Z :. oy :. ox) (getData cur')

           in   template7x7 oload zero

        | otherwise
        = error $ unlines 
                [ "mapStencil2: Your stencil is too big for this method."
                , " It must fit within a 7x7 tile to be compiled statically." ]


-- | Like above, but clamp out of bounds array values to the closest real value.
unsafeAppStencilCursor2_clamp
	:: forall r a
	.  Source r a
	=> (DIM2 -> DIM2 -> DIM2)
	-> Stencil DIM2 a
	-> Array r DIM2 a
	-> DIM2
	-> a

{-# INLINE unsafeAppStencilCursor2_clamp #-}
unsafeAppStencilCursor2_clamp shift
	   (StencilStatic sExtent zero loads)
	   arr cur

	| _ :. sHeight      :. sWidth	    <- sExtent
	, _ :. (I# aHeight) :. (I# aWidth)  <- extent arr
	, sHeight <= 7, sWidth <= 7
	= let
		-- Get data from the manifest array.
		{-# INLINE getData #-}
		getData :: DIM2 -> a
		getData (Z :. (I# y) :. (I# x))
		 = wrapLoadX x y

                {-# NOINLINE wrapLoadX #-}
		wrapLoadX :: Int# -> Int# -> a
		wrapLoadX !x !y
		 | x <# 0#	= wrapLoadY 0#      	   y
		 | x >=# aWidth	= wrapLoadY (aWidth -# 1#) y
		 | otherwise    = wrapLoadY x y

		{-# NOINLINE wrapLoadY #-}
		wrapLoadY :: Int# -> Int# -> a
		wrapLoadY !x !y
		 | y <#  0#	 = loadXY x 0#
		 | y >=# aHeight = loadXY x (aHeight -# 1#)
		 | otherwise     = loadXY x y

		{-# INLINE loadXY #-}
		loadXY :: Int# -> Int# -> a
		loadXY !x !y
		 = arr `unsafeIndex` (Z :. (I# y) :.  (I# x))

		-- Build a function to pass data from the array to our stencil.
		{-# INLINE oload #-}
		oload oy ox
		 = let	!cur' = shift (Z :. oy :. ox) cur
		   in	loads (Z :. oy :. ox) (getData cur')

	   in	template7x7 oload zero

        | otherwise
        = error $ unlines 
                [ "mapStencil2: Your stencil is too big for this method."
                , " It must fit within a 7x7 tile to be compiled statically." ]


-- | Data template for stencils up to 7x7.
template7x7
	:: (Int -> Int -> a -> a)
	-> a -> a

{-# INLINE template7x7 #-}
template7x7 f zero
 	= f (-3) (-3)  $  f (-3) (-2)  $  f (-3) (-1)  $  f (-3)   0  $  f (-3)   1  $  f (-3)   2  $ f (-3) 3
 	$ f (-2) (-3)  $  f (-2) (-2)  $  f (-2) (-1)  $  f (-2)   0  $  f (-2)   1  $  f (-2)   2  $ f (-2) 3
	$ f (-1) (-3)  $  f (-1) (-2)  $  f (-1) (-1)  $  f (-1)   0  $  f (-1)   1  $  f (-1)   2  $ f (-1) 3
	$ f   0  (-3)  $  f   0  (-2)  $  f   0  (-1)  $  f   0    0  $  f   0    1  $  f   0    2  $ f   0  3
	$ f   1  (-3)  $  f   1  (-2)  $  f   1  (-1)  $  f   1    0  $  f   1    1  $  f   1    2  $ f   1  3
	$ f   2  (-3)  $  f   2  (-2)  $  f   2  (-1)  $  f   2    0  $  f   2    1  $  f   2    2  $ f   2  3
	$ f   3  (-3)  $  f   3  (-2)  $  f   3  (-1)  $  f   3    0  $  f   3    1  $  f   3    2  $ f   3  3
	$ zero

