module Data.ListLike.Base 
    (
    ListLike(..),
    InfiniteListLike(..),
    zip, zipWith, sequence_
    ) where
import Prelude hiding (length, uncons, head, last, null, tail, map, filter, concat,
                       any, lookup, init, all, foldl, foldr, foldl1, foldr1,
                       maximum, minimum, iterate, span, break, takeWhile,
                       dropWhile, dropWhileEnd, reverse, zip, zipWith, sequence,
                       sequence_, mapM, mapM_, concatMap, and, or, sum,
                       product, repeat, replicate, cycle, take, drop,
                       splitAt, elem, notElem, unzip, lines, words,
                       unlines, unwords, foldMap)
import qualified Data.List as L
import Data.ListLike.FoldableLL
import qualified Control.Monad as M
import Data.Monoid
import Data.Maybe
class (FoldableLL full item, Monoid full) =>
    ListLike full item | full -> item where
    
    
    empty :: full
    empty = mempty
    
    singleton :: item -> full
    
    
    cons :: item -> full -> full
    cons item l = append (singleton item) l
    
    snoc :: full -> item -> full
    snoc l item = append l (singleton item)
    
    append :: full -> full -> full 
    append = mappend
    
    head :: full -> item
    head = maybe (error "head") fst . uncons
    
    uncons :: full -> Maybe (item, full)
    uncons x = if null x then Nothing else Just (head x, tail x) 
    
    last :: full -> item
    last l = case genericLength l of
                  (0::Integer) -> error "Called last on empty list"
                  1 -> head l
                  _ -> last (tail l)
    
    tail :: full -> full
    tail = maybe (error "tail") snd . uncons
    
    init :: full -> full
    init l
        | null l = error "init: empty list"
        | null xs = empty
        | otherwise = cons (head l) (init xs)
        where xs = tail l
    
    null :: full -> Bool
    null x = genericLength x == (0::Integer)
    
    length :: full -> Int
    length = genericLength
    
    
    map :: ListLike full' item' => (item -> item') -> full -> full'
    map func inp  
        | null inp = empty
        | otherwise = cons (func (head inp)) (map func (tail inp))
    
    rigidMap :: (item -> item) -> full -> full
    rigidMap = map
    
    reverse :: full -> full 
    reverse l = rev l empty
        where rev rl a
                | null rl = a
                | otherwise = rev (tail rl) (cons (head rl) a)
    
    intersperse :: item -> full -> full
    intersperse sep l
        | null l = empty
        | null xs = singleton x
        | otherwise = cons x (cons sep (intersperse sep xs))
        where x = head l
              xs = tail l
    
    
    
    
    concat :: (ListLike full' full, Monoid full) => full' -> full
    concat = fold
    
    concatMap :: (ListLike full' item') =>
                 (item -> full') -> full -> full'
    concatMap = foldMap
    
    rigidConcatMap :: (item -> full) -> full -> full
    rigidConcatMap = concatMap
    
    any :: (item -> Bool) -> full -> Bool
    any p = getAny . foldMap (Any . p)
    
    all :: (item -> Bool) -> full -> Bool
    all p = getAll . foldMap (All . p)
    
    maximum :: Ord item => full -> item
    maximum = foldr1 max
    
    minimum :: Ord item => full -> item
    minimum = foldr1 min
    
    
    replicate :: Int -> item -> full
    replicate = genericReplicate
    
    
    take :: Int -> full -> full
    take = genericTake
    
    drop :: Int -> full -> full
    drop = genericDrop
    
    splitAt :: Int -> full -> (full, full)
    splitAt = genericSplitAt
    
    takeWhile :: (item -> Bool) -> full -> full
    takeWhile func l 
        | null l = empty
        | func x = cons x (takeWhile func (tail l))
        | otherwise = empty
        where x = head l
    
    dropWhile :: (item -> Bool) -> full -> full
    dropWhile func l
        | null l = empty
        | func (head l) = dropWhile func (tail l)
        | otherwise = l
    
    dropWhileEnd :: (item -> Bool) -> full -> full
    dropWhileEnd func = foldr (\x xs -> if func x && null xs then empty else cons x xs) empty
    
    span :: (item -> Bool) -> full -> (full, full)
    span func l
        | null l = (empty, empty)
        | func x = (cons x ys, zs) 
        | otherwise = (empty, l)
       where (ys, zs) = span func (tail l)
             x = head l
    
    break :: (item -> Bool) -> full -> (full, full)
    break p = span (not . p)
    
    group :: (ListLike full' full, Eq item) => full -> full'
    group = groupBy (==)
    
    inits :: (ListLike full' full) => full -> full'
    inits l
        | null l = singleton empty
        | otherwise =
            append (singleton empty)
                   (map (cons (head l)) theinits)
            where theinits = asTypeOf (inits (tail l)) [l]
    
    tails :: ListLike full' full => full -> full'
    tails l
        | null l = singleton empty
        | otherwise = cons l (tails (tail l))
    
    
    isPrefixOf :: Eq item => full -> full -> Bool
    isPrefixOf needle haystack
        | null needle = True
        | null haystack = False
        | otherwise = (head needle) == (head haystack) && 
                      isPrefixOf (tail needle) (tail haystack)
    
    isSuffixOf :: Eq item => full -> full -> Bool
    isSuffixOf needle haystack = isPrefixOf (reverse needle) (reverse haystack)
    
    isInfixOf :: Eq item => full -> full -> Bool
    isInfixOf needle haystack = 
        any (isPrefixOf needle) thetails
        where thetails = asTypeOf (tails haystack) [haystack]
    
    
    stripPrefix :: Eq item => full -> full -> Maybe full
    stripPrefix xs ys = if xs `isPrefixOf` ys
                            then Just $ drop (length xs) ys
                            else Nothing
    
    stripSuffix :: Eq item => full -> full -> Maybe full
    stripSuffix xs ys = if xs `isSuffixOf` ys
                            then Just $ take (length ys  length xs) ys
                            else Nothing
    
    
    elem :: Eq item => item -> full -> Bool
    elem i = any (== i)
    
    notElem :: Eq item => item -> full -> Bool
    notElem i = all (/= i)
    
    find :: (item -> Bool) -> full -> Maybe item
    find f l = case findIndex f l of
                    Nothing -> Nothing
                    Just x -> Just (index l x)
    
    filter :: (item -> Bool) -> full -> full 
    filter func l 
        | null l = empty
        | func (head l) = cons (head l) (filter func (tail l))
        | otherwise = filter func (tail l)
    
    partition :: (item -> Bool) -> full -> (full, full)
    partition p xs = (filter p xs, filter (not . p) xs)
    
    
    index :: full -> Int -> item
    index l i 
        | null l = error "index: index not found"
        | i < 0 = error "index: index must be >= 0"
        | i == 0 = head l
        | otherwise = index (tail l) (i  1)
    
    elemIndex :: Eq item => item -> full -> Maybe Int
    elemIndex e l = findIndex (== e) l
    
    elemIndices :: (Eq item, ListLike result Int) => item -> full -> result
    elemIndices i l = findIndices (== i) l
    
    findIndex :: (item -> Bool) -> full -> Maybe Int
    findIndex f = listToMaybe . findIndices f
    
    findIndices :: (ListLike result Int) => (item -> Bool) -> full -> result
    findIndices p xs = map snd $ filter (p . fst) $ thezips
        where thezips = asTypeOf (zip xs [0..]) [(head xs, 0::Int)]
    
    
    sequence :: (Monad m, ListLike fullinp (m item)) =>
                fullinp -> m full
    sequence l = foldr func (return empty) l
        where func litem results = 
                do x <- litem
                   xs <- results
                   return (cons x xs)
    
    mapM :: (Monad m, ListLike full' item') => 
            (item -> m item') -> full -> m full'
    mapM func l = sequence mapresult
            where mapresult = asTypeOf (map func l) []
    
    rigidMapM :: Monad m => (item -> m item) -> full -> m full
    rigidMapM = mapM
    
    
    nub :: Eq item => full -> full
    nub = nubBy (==)
    
    delete :: Eq item => item -> full -> full
    delete = deleteBy (==)
    
    deleteFirsts :: Eq item => full -> full -> full
    deleteFirsts = foldl (flip delete)
    
    union :: Eq item => full -> full -> full
    union = unionBy (==)
    
    intersect :: Eq item => full -> full -> full
    intersect = intersectBy (==)
    
    
    sort :: Ord item => full -> full
    sort = sortBy compare
    
    insert :: Ord item => item -> full -> full 
    insert = insertBy compare
    
    
    toList :: full -> [item]
    toList = fromListLike
    
    fromList :: [item] -> full 
    fromList [] = empty
    fromList (x:xs) = cons x (fromList xs)
    
    fromListLike :: ListLike full' item => full -> full'
    fromListLike = map id
    
    
    
    nubBy :: (item -> item -> Bool) -> full -> full
    nubBy f l = nubBy' l (empty :: full)
     where
      nubBy' ys xs
        | null ys              = empty
        | any (f (head ys)) xs = nubBy' (tail ys) xs
        | otherwise            = let y = head ys
                                 in  cons y (nubBy' (tail ys) (cons y xs))
    
    deleteBy :: (item -> item -> Bool) -> item -> full -> full
    deleteBy func i l
        | null l = empty
        | otherwise =
            if func i (head l)
               then tail l
               else cons (head l) (deleteBy func i (tail l))
    
    deleteFirstsBy :: (item -> item -> Bool) -> full -> full -> full
    deleteFirstsBy func = foldl (flip (deleteBy func))
    
    unionBy :: (item -> item -> Bool) -> full -> full -> full
    unionBy func x y =
        append x $ foldl (flip (deleteBy func)) (nubBy func y) x
    
    intersectBy :: (item -> item -> Bool) -> full -> full -> full
    intersectBy func xs ys = filter (\x -> any (func x) ys) xs
    
    groupBy :: (ListLike full' full, Eq item) => 
                (item -> item -> Bool) -> full -> full'
    groupBy eq l
        | null l = empty
        | otherwise = cons (cons x ys) (groupBy eq zs)
                      where (ys, zs) = span (eq x) xs
                            x = head l
                            xs = tail l
    
    sortBy :: (item -> item -> Ordering) -> full -> full 
    sortBy cmp = foldr (insertBy cmp) empty
    
    insertBy :: (item -> item -> Ordering) -> item ->
                full -> full 
    insertBy cmp x ys
        | null ys = singleton x
        | otherwise = case cmp x (head ys) of
                        GT -> cons (head ys) (insertBy cmp x (tail ys))
                        _ ->  cons x ys
    
    
    genericLength :: Num a => full -> a
    genericLength l = calclen 0 l
        where calclen !accum cl =
                  if null cl
                     then accum
                     else calclen (accum + 1) (tail cl)
    
    genericTake :: Integral a => a -> full -> full
    genericTake n l
        | n <= 0 = empty
        | null l = empty
        | otherwise = cons (head l) (genericTake (n  1) (tail l))
    
    genericDrop :: Integral a => a -> full -> full
    genericDrop n l 
        | n <= 0 = l
        | null l = l
        | otherwise = genericDrop (n  1) (tail l)
    
    genericSplitAt :: Integral a => a -> full -> (full, full)
    genericSplitAt n l = (genericTake n l, genericDrop n l)
    
    genericReplicate :: Integral a => a -> item -> full
    genericReplicate count x 
        | count <= 0 = empty
        | otherwise = map (\_ -> x) [1..count]
#if __GLASGOW_HASKELL__ >= 708
    
#endif
class (ListLike full item) => InfiniteListLike full item | full -> item where
    
    iterate :: (item -> item) -> item -> full
    iterate f x = cons x (iterate f (f x))
    
    repeat :: item -> full
    repeat x = xs
        where xs = cons x xs
    
    cycle :: full -> full
    cycle xs 
        | null xs = error "ListLike.cycle: empty list"
        | otherwise = xs' where xs' = append xs xs'
instance ListLike [a] a where
    empty = []
    singleton x = [x]
    cons x l = x : l
    snoc l x = l ++ [x]
    append = (++)
    head = L.head
    last = L.last
    tail = L.tail
    init = L.init
    null = L.null
    length = L.length
    map f = fromList . L.map f
    rigidMap = L.map
    reverse = L.reverse
    intersperse = L.intersperse
    toList = id
    fromList = id
    
    concat = L.concat . toList
    
    rigidConcatMap = L.concatMap
    any = L.any
    all = L.all
    maximum = L.maximum
    minimum = L.minimum
    
    
    replicate = L.replicate
    take = L.take
    drop = L.drop
    splitAt = L.splitAt
    takeWhile = L.takeWhile
    dropWhile = L.dropWhile
    span = L.span
    break = L.break
    group = fromList . L.group
    inits = fromList . L.inits
    tails = fromList . L.tails
    isPrefixOf = L.isPrefixOf
    isSuffixOf = L.isSuffixOf
    isInfixOf = L.isInfixOf
    stripPrefix = L.stripPrefix
    elem = L.elem
    notElem = L.notElem
    find = L.find
    filter = L.filter
    partition = L.partition
    index = (L.!!)
    elemIndex = L.elemIndex
    elemIndices item = fromList . L.elemIndices item
    findIndex = L.findIndex
    sequence = M.sequence . toList
    
    nub = L.nub
    delete = L.delete
    deleteFirsts = (L.\\)
    union = L.union
    intersect = L.intersect
    sort = L.sort
    groupBy func = fromList . L.groupBy func
    unionBy = L.unionBy
    intersectBy = L.intersectBy
    sortBy = L.sortBy
    insert = L.insert
    genericLength = L.genericLength
zip :: (ListLike full item,
          ListLike fullb itemb,
          ListLike result (item, itemb)) =>
          full -> fullb -> result
zip = zipWith (\a b -> (a, b))
zipWith :: (ListLike full item,
            ListLike fullb itemb,
            ListLike result resultitem) =>
            (item -> itemb -> resultitem) -> full -> fullb -> result
zipWith f a b
    | null a = empty
    | null b = empty
    | otherwise = cons (f (head a) (head b)) (zipWith f (tail a) (tail b))