------------------------------------------------------------------------
-- |
-- Module      :  ALife.Creatur.Util
-- Copyright   :  (c) Amy de Buitléir 2011-2014
-- License     :  BSD-style
-- Maintainer  :  amy@nualeargais.ie
-- Stability   :  experimental
-- Portability :  portable
--
-- Utility functions that don't fit anywhere else.
--
------------------------------------------------------------------------
module ALife.Creatur.Util
  (
    -- * Integers
    ilogBase,
    isPowerOf,
    isqrt,
    perfectSquare,
    -- * Arrays
    cropRect,
    cropSquare,
    -- * Sequences
    replaceElement,
    reverseLookup,
    rotate,
    safeReplaceElement,
    shuffle,
    -- * Bits/Booleans
    boolsToBits,
    showBin,
    -- * Monads
    stateMap,
    fromEither,
    catEithers,
    modifyLift,
    getLift
--    constrain,
  ) where

import Control.Monad (forM_, liftM)
import Control.Monad.Random (Rand, RandomGen, getRandomRs)
import Control.Monad.State (StateT(..), get, lift, put)
import Data.Array.ST (runSTArray)
import Data.Char (intToDigit)
import Data.List.Split (chunksOf)
import GHC.Arr (elems, listArray, readSTArray, thawSTArray, writeSTArray)
import Numeric (showIntAtBase)

-- constrain :: Ord a => (a, a) -> a -> a
-- constrain (a,b) x | b < a     = error "Invalid range"
--                   | x < a     = a
--                   | x > b     = b
--                   | otherwise = x

-- | From <http://www.haskell.org/haskellwiki/Random_shuffle>
shuffle :: RandomGen g => [a] -> Rand g [a]
shuffle xs = do
  let l = length xs
  rands <- take l `fmap` getRandomRs (0, l-1)
  let ar = runSTArray $ do
      ar' <- thawSTArray $ listArray (0, l-1) xs
      forM_ (zip [0..(l-1)] rands) $ \(i, j) -> do
          vi <- readSTArray ar' i
          vj <- readSTArray ar' j
          writeSTArray ar' j vi
          writeSTArray ar' i vj
      return ar'
  return (elems ar)

-- | @'safeReplaceElement' xs n x@ returns a copy of @xs@ in which the @n@th
--   element (if it exists) has been replaced with @x@.
safeReplaceElement :: [a] -> Int -> a -> [a]
safeReplaceElement xs i x =
  if i >= 0 && i < length xs
    then replaceElement xs i x
    else xs

-- | @'replaceElement' xs n x@ returns a copy of @xs@ in which the @n@th
--   element has been replaced with @x@. Causes an exception if @xs@ has
--   fewer than @n+1@ elements. Compare with @'safeReplaceElement'@.
replaceElement :: [a] -> Int -> a -> [a]
replaceElement xs i x = 
  if 0 <= i && i < length xs then fore ++ (x : aft) else xs
  where fore = take i xs
        aft = drop (i+1) xs

-- | Assuming @xs@ is a sequence containing the elements of a square matrix,
--   @'cropSquare' n xs@ returns the elements of the submatrix of size @n@x@n@,
--   centred within the original matrix @xs@.
--
--   Example: Suppose we have a /5/x/5/ matrix and we want to extract the
--   central /3/x/3/ submatrix, as illustrated below.
--
-- > a b c d e
-- > f g h i j            g h i
-- > k l m n o    --->    l m n
-- > p q r s t            q r s
-- > u v w x y
--
--   We can represent the elements of the original matrix as @[\'a\'..\'y\']@.
--   The elements of the submatrix are
--   @[\'g\', \'h\', \'i\', \'l\', \'m\', \'n\', \'q\', \'r\', \'s\']@,
--   or equivalently, @\"ghilmnqrs\"@. And that is what
--   @'cropSquare' 3 [\'a\'..\'y\']@ returns.
cropSquare :: Int -> [a] -> [a]
cropSquare n xs | n <= 0     = []
                | n < m     = 
                    cropRect (margin, margin) (margin+n-1, margin+n-1) xs m
                | otherwise = take (m*m) xs
  where m = (isqrt . length) xs
        margin = (m - n) `div` 2

-- | Assuming @xs@ is a sequence containing the elements of a matrix with @k@
--   columns, @'cropRect' (a,b) (c, d) k xs@ returns the elements of the
--   submatrix from @(a,b)@ in the upper left corner to @(c,d)@ in the lower
--   right corner).
--   Note: Matrix indices begin at @(0,0)@.
--
--   Example: Suppose we have a /4/x/6/ matrix and we want to extract the
--   submatrix from (1,2) to (2,4), as illustrated below.
--
-- > a b c d e f
-- > g h i j k l    --->   i j k
-- > m n o p q r           o p q
-- > s t u v w x
--
--   We can represent the elements of the original matrix as @[\'a\'..\'x\']@.
--   The elements of the submatrix are
--   @[\'i\', \'j\', \'k\', \'o\', \'p\', \'q\']@, or equivalently,
--   @\"ijkopq\"@. And that is what @'cropRect' (1,2) (2,4) 6 [\'a\'..\'x\']@
--   returns.
cropRect :: (Int, Int) -> (Int, Int) -> [a] -> Int -> [a]
cropRect (a,b) (c, d) xs k = concatMap f selectedRows
  where rows = if k <= 0 then [] else chunksOf k xs
        selectedRows = safeSlice a c rows
        f = safeSlice b d

safeSlice :: Int -> Int -> [a] -> [a]
safeSlice a b = drop a . take (b+1)

-- | @'isqrt' n@ returns the greatest integer not greater than the square root
--   of @n@.
isqrt :: (Integral a, Integral b) => a -> b
isqrt n = (floor . sqrt) n'
  where n' = fromIntegral n :: Float

-- | @'ilogBase' n m@ returns the greatest integer not greater than the log
--   base n of @m@.
ilogBase :: (Integral a, Integral b, Integral c) => a -> b -> c
ilogBase n m = (floor . logBase n') m'
  where n' = fromIntegral n :: Float
        m' = fromIntegral m :: Float

-- | @'perfectSquare' n@ returns @True@ if @n@ is a perfect square (i.e., if 
--   there exists an _integer_ m such that m*m = n)
perfectSquare :: Integral a => a -> Bool
perfectSquare n = n == m*m
  where m = isqrt n

-- | @n 'isPowerOf' m@ returns @True@ if @n@ is a power of m (i.e., if 
--   there exists an _integer_ k such that m^k = n)
isPowerOf :: Integral a => a -> a -> Bool
isPowerOf n m = n == m^k
  where k = ilogBase m n :: Int

reverseLookup :: (Eq b) => b -> [(a,b)] -> Maybe a
reverseLookup _ []          =  Nothing
reverseLookup value ((x,y):xys)
    | value == y =  Just x
    | otherwise  =  reverseLookup value xys

stateMap :: Monad m => (s -> t) -> (t -> s) -> StateT s m a -> StateT t m a
stateMap f g (StateT h) = StateT $ liftM (fmap f) . h . g

-- | The 'fromEither' function takes a default value and an 'Either'
--   value.  If the 'Either' is 'Left', it returns the default value;
--   otherwise, it returns the value contained in the 'Right'.
fromEither     :: a -> Either e a -> a
fromEither d x = case x of {Left _ -> d; Right v  -> v}

-- | Takes a list of 'Either's and returns a list of all the 'Right'
--   values. 
catEithers              :: [Either e a] -> [a]
catEithers ls = [x | Right x <- ls]

-- | Like modify, but the function that maps the old state to the
--   new state operates in the inner monad.
--   For example,
--   
--   > s <- get
--   > s' = lift $ f s
--   > put s'
--   
--   can be replaced with
--   
--   > modifyLift f
modifyLift :: Monad m => (s -> m s) -> StateT s m ()
modifyLift f = get >>= lift . f >>= put

-- | Invoke a function in the inner monad, and pass the state as
--   a parameter.
--   Similar to modifyLift, but the function being invoked doesn't
--   have a return value, so the state is not modified.
--   For example,
--   
--   > s <- get
--   > s' = lift $ f s
--   
--   can be replaced with
--   
--   > getLift f
getLift :: Monad m => (s -> m ()) -> StateT s m ()
getLift f = get >>= lift . f >> return ()

rotate :: [a] -> [a]
rotate [] = []
rotate (x:xs) = xs ++ [x]

-- | Convert a list of bits to a string of @0@s and @1@s.
boolsToBits :: [Bool] -> String
boolsToBits = map (\b -> if b then '1' else '0')

-- | Show /non-negative/ 'Integral' numbers in binary.
showBin :: (Integral a,Show a) => a -> ShowS
showBin = showIntAtBase 2 intToDigit