-----------------------------------------------------------------------------
-- |
-- Module      :  Math.Geometry.GridMap
-- Copyright   :  (c) Amy de Buitléir 2012
-- License     :  BSD-style
-- Maintainer  :  amy@nualeargais.ie
-- Stability   :  experimental
-- Portability :  portable
--
-- Ordered maps from tiles on a grid to values.
-- This module is a wrapper around @'Math.Geometry.Grid'@ and @'Data.Map'@,
-- in order to combine the functionality of grids and maps into a single type.
--
-----------------------------------------------------------------------------
{-# LANGUAGE UnicodeSyntax, MultiParamTypeClasses, FlexibleInstances, 
    UndecidableInstances #-}

module Math.Geometry.GridMap
  (
    -- * Differences between @GridMap@ and @Map@.
    -- $Compare

    -- * Map type
    GridMap,

    -- * Construction
    lazyGridMap,

    -- * Grid functions
    indices,
    distance,
    size,
    neighbours,
    contains,
    viewpoint,
    tileCount,
    empty,
    nonEmpty,
    

    -- * Map functions
    -- ** Operators
    (!),

    -- ** Query
    lookup,
    findWithDefault,

    -- ** Update
    adjust,
    adjustWithKey,

    -- ** Map
    map,
    mapWithKey,
    mapAccum,
    mapAccumWithKey,

    -- ** Folds
    fold,
    foldWithKey,
    fold',
    foldWithKey',

    -- ** Conversion
    elems,
    keysSet,

    -- ** Lists
    toList
  ) where

import Prelude hiding (lookup, map)
import qualified Data.Map as M
--import qualified Data.Map.Strict as Strict (Map)
import Data.Maybe (fromMaybe)
import Data.Set (Set)
import Math.Geometry.Grid (Grid(..))

-- | A Map from tile positions in a grid to values. 
data GridMap g k v = LGridMap { toGrid  g, toMap  M.Map k v }
  deriving Eq
-- Future: add alternative constructor for strict maps

instance (Show g, Show v)  Show (GridMap g k v) where
  show (LGridMap g m) = "lazyGridMap (" ++ show g ++ ") " ++ show (M.elems m)

-- | Construct a grid map which is strict in the keys (tile positions), but
--   lazy in the values.
lazyGridMap  (Ord k, Grid g s k)  g  [v]  GridMap g k v
lazyGridMap g vs = LGridMap g (M.fromList kvs)
  where kvs = zip ks vs
        ks = indices g

instance (Eq k, Grid g s k)  Grid (GridMap g k v) s k where
  indices = indices . toGrid
  distance g = distance (toGrid g)
  size = size . toGrid
  neighbours g k = toGrid g `neighbours` k
  contains g k = toGrid g `contains` k
  viewpoint g k = toGrid g `viewpoint` k
  tileCount  = tileCount . toGrid
  empty = empty . toGrid
  nonEmpty = nonEmpty . toGrid

-- | /O(min(n,W))/. Find the value at a tile position in the grid.
-- Calls 'error' when the element can not be found.
(!)  Ord k  GridMap g k v  k  v
(!) m k = toMap m M.! k

modifyMap  (M.Map k a  M.Map k b)  GridMap g k a  GridMap g k b
modifyMap f m = m { toMap = f (toMap m)}

applyToMap  (M.Map k v  c)  GridMap g k v  c
applyToMap f = f . toMap

-- | /O(min(n,W))/. Lookup the value at a tile position in the grid map.
lookup  Ord k  k  GridMap g k v  Maybe v
lookup k = applyToMap $ M.lookup k

-- | /O(min(n,W))/. Adjust a value at a specific tile position. When the tile
-- is not within the bounds of the grid map, the original grid map is
-- returned.
adjust  Ord k  (v  v)  k  GridMap g k v  GridMap g k v
adjust f k = modifyMap (M.adjust f k)

-- | /O(min(n,W))/. Adjust a value at a specific key. When the tile
-- is not within the bounds of the grid map, the original grid map is
-- returned.
adjustWithKey  Ord k  (k  v  v)  k  GridMap g k v  GridMap g k v
adjustWithKey f k = modifyMap (M.adjustWithKey f k)

-- | /O(min(n,W))/. The expression @('findWithDefault' def k map)@
-- returns the value at tile position @k@ or returns @def@ when the tile
-- is not within the bounds of the grid map.
findWithDefault  Ord k  v  k  GridMap g k v  v
findWithDefault v k m = fromMaybe v $ applyToMap (M.lookup k) m

-- | /O(n)/. Map a function over all values in the grid map.
map  (a  b)  GridMap g k a  GridMap g k b
map f = modifyMap (M.map f)

-- | /O(n)/. Map a function over all values in the grid map.
mapWithKey  (k  a  b)  GridMap g k a  GridMap g k b
mapWithKey f = modifyMap (M.mapWithKey f)

-- | /O(n)/. The function @'mapAccum'@ threads an accumulating
-- argument through the grid map.
-- WARNING: The order in which the elements are processed is not guaranteed.
mapAccum  (a  b  (a, c))  a  GridMap g k b  (a, GridMap g k c)
mapAccum f = mapAccumWithKey (\a _ x  f a x)

-- | /O(n)/. The function @'mapAccumWithKey'@ threads an accumulating
-- argument through the grid map.
-- WARNING: The order in which the elements are processed is not guaranteed.
mapAccumWithKey  
  (a  k  b  (a, c))  a  GridMap g k b  (a, GridMap g k c)
mapAccumWithKey f a gm = (b, gm {toMap=m'})
  where (b, m') = applyToMap (M.mapAccumWithKey f a) gm

-- | /O(n)/. Fold the values in the grid map using the given left-associative
-- binary operator.
-- WARNING: The order in which the elements are processed is not guaranteed.
fold  (a  b  a)  a  GridMap g k b  a
fold f x = applyToMap (M.foldl f x)

-- | /O(n)/. Fold the keys and values in the grid map using the given 
-- left-associative binary operator.
-- WARNING: The order in which the elements are processed is not guaranteed.
foldWithKey  (a  k  b  a)  a  GridMap g k b  a
foldWithKey f x = applyToMap (M.foldlWithKey f x)

-- | /O(n)/. A strict version of 'fold'.
fold'  (a  b  a)  a  GridMap g k b  a
fold' f x = applyToMap (M.foldl' f x)

-- | /O(n)/. A strict version of 'foldWithKey'.
foldWithKey'  (a  k  b  a)  a  GridMap g k b  a
foldWithKey' f x = applyToMap (M.foldlWithKey' f x)

-- | /O(n)/.
-- Return all elements of the grid map. The order is not guaranteed.
elems  GridMap g k a  [a]
elems = applyToMap M.elems

-- | /O(n*min(n,W))/. The set of all tile positions in the grid map.
keysSet  GridMap g k a  Set k
keysSet = applyToMap M.keysSet

-- | /O(n)/. Returns all key (tile position)\/value pairs in the grid map.
toList  GridMap g k a  [(k, a)]
toList = applyToMap M.toList

{- $Compare
Some functions in @Data.Map@ have been replaced in @GridMap@.
These changes are listed in the table below.

@
Map function    | corresponding GridMap function
----------------+-------------------------------
assocs          | 'toList'
empty           | 'lazyGridMap' g []
foldl           | 'fold'
foldl'          | 'fold''
foldlWithKey    | 'foldWithKey'
foldlWithKey'   | 'foldWithKey''
foldr           | 'fold'
foldr'          | 'fold''
foldrWithKey    | 'foldWithKey'
foldrWithKey'   | 'foldWithKey''
fromList        | 'lazyGridMap'
fromListWithKey | 'lazyGridMap'
fromListWith    | 'lazyGridMap'
fromSet         | 'lazyGridMap'
keys            | 'indices'
member          | 'inGrid'
notMember       | not 'inGrid'
null            | 'empty'
singleton       | 'lazyGridMap' g [v]
size            | 'size', 'tileCount'
@

The functions (\\), @alter@, @delete@, @deleteFindMax@, @deleteFindMin@,
@deleteMax@, @deleteMin@, @difference@, @differenceWith@, @differenceWithKey@,
@filter@, @filterWithKey@, @insert@, @insertLookupWithKey@, @insertWith@,
@insertWithKey@, @intersection@, @intersectionWith@, @intersectionWithKey@,
@isProperSubmapOf@, @isProperSubmapOfBy@, @isSubmapOf@, @isSubmapOf@,
@isSubmapOfBy@, @mapEither@, @mapEitherWithKey@, @mapKeys@, @mapKeysWith@,
@mapMaybe@, @mapMaybeWithKey@, @mergeWithKey@, @partition@,
@partitionWithKey@, @split@, @splitLookup@, @traverseWithKey@, @union@,
@unions@, @unionsWith@, @unionWith@, @unionWithKey@, @update@,
@updateLookupWithKey@ and @updateWithKey@ are not implemented because the
resulting map might have different dimensions than the original, or because
they combine maps of different dimensions. 
As a result, these functions may not be as useful for grid maps.
If you need one of these functions, you can extract the map using @toMap@
and apply the function from @Data.Map@ to the result.

The functions @deleteAt@, @elemAt@, @findIndex@, @findMax@, @findMin@, 
@fromAscList@, @fromAscListWith@, @fromAscListWithKey@, @fromDistinctAscList@,
@lookupGE@, @lookupGT@, @lookupIndex@, @lookupLE@, @lookupLT@, 
@mapAccumRWithKey@, @mapKeysMonotonic@, @maxView@, @maxViewWithKey@, 
@minView@, @minViewWithKey@, @toAscList@, @toDescList@, @updateAt@, 
@updateMax@, @updateMaxWithKey@, @updateMin@ and @updateMinWithKey@ are not
implemented because they rely on a meaningful ordering of keys.
While tile positions can be ordered (e.g., @(1,2) < (2,1)@), the ordering
may not be relevant to grid maps.
(Comparisons such as /east of/ or /south of/ may be more meaningful.)
If you need one of these functions, you can extract the map using @toMap@
and apply the function from @Data.Map@ to the result.

The debugging functions @showTree@, @showTreeWith@ and @valid@ are not
implemented.
If you need one of these functions, you can extract the map using @toMap@
and apply the function from @Data.Map@ to the result.
-}