{-# LANGUAGE NoImplicitPrelude #-}
{- |
Functions that are counterparts of the @generic@ functions in "Data.List"
using NumericPrelude.Numeric type classes.
For input arguments we use the restrictive @ToInteger@ constraint,
although in principle @RealRing@ would be enough.
However we think that @take 0.5 xs@ is rather a bug than a feature,
thus we forbid fractional types.
On the other hand fractional types as result can be quite handy,
e.g. in @average xs = sum xs / length xs@.
-}
module NumericPrelude.List.Generic
   ((!!), lengthLeft, lengthRight, replicate,
    take, drop, splitAt,
    findIndex, elemIndex, findIndices, elemIndices,
   ) where

import NumericPrelude.List.Checked ((!!), )

import qualified Algebra.ToInteger  as ToInteger
import qualified Algebra.Ring       as Ring
import Algebra.Ring (one, )
import Algebra.Additive (zero, (+), (-), )

import qualified Data.Maybe         as Maybe
import Data.Tuple.HT (mapFst, )

import NumericPrelude.Base as List
   hiding (take, drop, splitAt, length, replicate, (!!), )


replicate :: (ToInteger.C n) => n -> a -> [a]
replicate n x = take n (List.repeat x)

take :: (ToInteger.C n) => n -> [a] -> [a]
take _ [] = []
take n (x:xs) =
   if n<=zero
     then []
     else x : take (n-one) xs

drop :: (ToInteger.C n) => n -> [a] -> [a]
drop _ [] = []
drop n xt@(_:xs) =
   if n<=zero
     then xt
     else drop (n-one) xs

splitAt :: (ToInteger.C n) => n -> [a] -> ([a], [a])
splitAt _ [] = ([], [])
splitAt n xt@(x:xs) =
   if n<=zero
     then ([], xt)
     else mapFst (x:) $ splitAt (n-one) xs


{- |
Left associative length computation
that is appropriate for types like @Integer@.
-}
lengthLeft :: (Ring.C n) => [a] -> n
lengthLeft = List.foldl (\n _ -> n+one) zero

{- |
Right associative length computation
that is appropriate for types like @Peano@ number.
-}
lengthRight :: (Ring.C n) => [a] -> n
lengthRight = List.foldr (\_ n -> one+n) zero

elemIndex :: (Ring.C n, Eq a) => a -> [a] -> Maybe n
elemIndex e = findIndex (e==)

elemIndices :: (Ring.C n, Eq a) => a -> [a] -> [n]
elemIndices e = findIndices (e==)

findIndex :: Ring.C n => (a -> Bool) -> [a] -> Maybe n
findIndex p = Maybe.listToMaybe . findIndices p

findIndices :: Ring.C n => (a -> Bool) -> [a] -> [n]
findIndices p =
   map fst .
   filter (p . snd) .
   zip (iterate (one+) zero)