{-# LINE 1 "FOV.hsc" #-}
{-# OPTIONS -fffi -fglasgow-exts #-}
{-# LINE 2 "FOV.hsc" #-}
-- | HFOV is a library for calculating field of view in a 2D raster grid, such
-- as those found in roguelike games.
--
-- Thanks to Greg McIntyre for the original C library to which this Haskell
-- library binds. It can be found at <http://libfov.sourceforge.net/wiki>.
module FOV (
		Settings,
		Shape(..),
		Direction(..),
		newSettings,    -- IO Settings
		setShape,       -- Settings -> Shape -> IO ()
		setOpaqueApply, -- Settings -> Bool -> IO ()
		circle,
		beam
	) where

import Foreign.Ptr
import Foreign.ForeignPtr


{-# LINE 22 "FOV.hsc" #-}

type OpacityTestFn a = Ptr a -> Int -> Int -> IO Bool
foreign import ccall "wrapper" mkOpacityTestFn :: OpacityTestFn a -> IO (FunPtr (OpacityTestFn a))

type ApplyFn map src = Ptr map -> Int -> Int -> Int -> Int -> Ptr src -> IO ()
foreign import ccall "wrapper" mkApplyFn :: ApplyFn a b -> IO (FunPtr (ApplyFn a b))

data SettingsRaw

-- | Opaque data structure for holding information about FOV calculation.
newtype Settings = Settings (ForeignPtr SettingsRaw)

-- | Shape of the field.
data Shape
	-- | Limit the field of view to a circle radius R by precalculating the
	-- circle shape. This consumes memory at the rate of 4*(R+2) bytes per R
	-- used in calls to fovCircle. Each radius is only calculated once.
	= CirclePrecalculate
	-- | Limit the field to a circle shape calculated on the fly.
	| Circle
	-- | Limit the field to an octagon with maximum radius R.
	| Octagon
	-- | Limit the field to an RxR square.
	| Square

data Direction = East | NorthEast | North | NorthWest | West | SouthWest | South | SouthEast

rawshape :: (Num t) => Shape -> t
rawshape CirclePrecalculate = 0
{-# LINE 51 "FOV.hsc" #-}
rawshape Square = 1
{-# LINE 52 "FOV.hsc" #-}
rawshape Circle = 2
{-# LINE 53 "FOV.hsc" #-}
rawshape Octagon = 3
{-# LINE 54 "FOV.hsc" #-}

rawdirection :: (Num t) => Direction -> t
rawdirection East = 0
{-# LINE 57 "FOV.hsc" #-}
rawdirection NorthEast = 1
{-# LINE 58 "FOV.hsc" #-}
rawdirection North = 2
{-# LINE 59 "FOV.hsc" #-}
rawdirection NorthWest = 3
{-# LINE 60 "FOV.hsc" #-}
rawdirection West = 4
{-# LINE 61 "FOV.hsc" #-}
rawdirection SouthWest = 5
{-# LINE 62 "FOV.hsc" #-}
rawdirection South = 6
{-# LINE 63 "FOV.hsc" #-}
rawdirection SouthEast = 7
{-# LINE 64 "FOV.hsc" #-}

foreign import ccall unsafe new_fov_settings :: IO (Ptr SettingsRaw)
foreign import ccall unsafe fov_settings_set_shape :: Ptr SettingsRaw -> Int -> IO ()
foreign import ccall unsafe fov_settings_set_opaque_apply :: Ptr SettingsRaw -> Int -> IO ()
foreign import ccall unsafe fov_settings_set_apply_lighting_function :: Ptr SettingsRaw -> FunPtr (ApplyFn a b) -> IO ()
foreign import ccall unsafe fov_settings_set_opacity_test_function :: Ptr SettingsRaw -> FunPtr (OpacityTestFn a) -> IO ()
foreign import ccall unsafe "&fov_settings_free" fov_settings_free :: FunPtr (Ptr SettingsRaw -> IO ())
foreign import ccall fov_circle :: Ptr SettingsRaw -> Ptr map -> Ptr src -> Int -> Int -> Int -> IO ()
foreign import ccall fov_beam :: Ptr SettingsRaw -> Ptr map -> Ptr src -> Int -> Int -> Int -> Int -> Float -> IO ()

-- | Create a new FOV settings structure.
newSettings :: IO Settings
newSettings = do
	s <- new_fov_settings >>= newForeignPtr fov_settings_free
	return $ Settings s

-- | Set the shape of the field of view. The default is CirclePrecalculate.
setShape :: Settings -> Shape -> IO ()
setShape (Settings fps) sh = withForeignPtr fps $ \s -> do
	fov_settings_set_shape s (rawshape sh)

-- | Sets whether or not to apply lighting to opaque squares.
setOpaqueApply :: Settings -> Bool -> IO ()
setOpaqueApply (Settings fps) a = withForeignPtr fps $ \s -> do
	fov_settings_set_opaque_apply s (if a then (0) else (1))
{-# LINE 89 "FOV.hsc" #-}

-- | Cast a 360 degree field of view.
circle :: Settings                -- ^The FOV settings structure to use
       -> (Int,Int)               -- ^The centre of the field
       -> Int                     -- ^The radius
       -> (Int -> Int -> IO ())   -- ^The function to be called in order to apply light to a certain position
       -> (Int -> Int -> IO Bool) -- ^The function to determine the opacity of a certain cell
       -> IO ()
circle (Settings fps) (x,y) r apply opaque = withForeignPtr fps $ \s -> do
	fov_settings_set_apply_lighting_function s =<< mkApplyFn (\_ x' y' _ _ _ -> apply x' y')
	fov_settings_set_opacity_test_function s =<< mkOpacityTestFn (\_ x' y' -> opaque x' y')
	fov_circle s nullPtr nullPtr x y r

-- | Cast a beam.
beam :: Settings                -- ^The FOV settings structure to use
     -> (Int,Int)               -- ^Origin of the beam
     -> Int                     -- ^Length (radius) of the beam
     -> Direction               -- ^Direction of the centre of the beam
     -> Float                   -- ^Width of the beam (in degrees)
     -> (Int -> Int -> IO ())   -- ^Light application function
     -> (Int -> Int -> IO Bool) -- ^Opacity test function
     -> IO ()
beam (Settings fps) (x,y) r dir angle apply opaque = withForeignPtr fps $ \s -> do
	fov_settings_set_apply_lighting_function s =<< mkApplyFn (\_ x' y' _ _ _ -> apply x' y')
	fov_settings_set_opacity_test_function s =<< mkOpacityTestFn (\_ x' y' -> opaque x' y')
	fov_beam s nullPtr nullPtr x y r (rawdirection dir) angle