-- Author:     Andy Stewart <lazycat.manatee@gmail.com>
-- Maintainer: Andy Stewart <lazycat.manatee@gmail.com>
-- 
-- Copyright (C) 2010 Andy Stewart, all rights reserved.
-- 
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- any later version.
-- 
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
-- 
-- You should have received a copy of the GNU General Public License
-- along with this program.  If not, see <http://www.gnu.org/licenses/>.

{-# LANGUAGE TransformListComp #-}
module Manatee.Toolkit.General.List where

import Control.Arrow
import Control.Monad
import Data.List
import Data.Maybe
import Data.Monoid
import Manatee.Toolkit.General.Basic

-- | Return element of list with given index.
(?!) :: [a] -> Int -> Maybe a
[] ?! _ = Nothing
xs ?! n
    | n < 0 
        = Nothing
    | n >= length xs 
        = Nothing
    | otherwise               
        = listToMaybe . drop n $ xs

-- | Intersect element with list from end, don't including front or middle element.
-- Return null if haven't intersection element.
intersectEnd :: Ord a => [a] -> [a] -> [a]
intersectEnd xs ys = reverse $ intersectFront (reverse xs) (reverse ys)

-- | Intersect element with list from front, don't including end or middle element.
-- Return null if haven't intersection element.
intersectFront :: Ord a => [a] -> [a] -> [a]
intersectFront xs ys = [x | (x,y) <- zip xs ys, then takeWhile by x == y]

-- | Delay list with given start index.
delay :: Int -> [a] -> [Int]
delay n (_:xs) = n : delay (n + 1) xs
delay _ _      = []

-- | Index of list
listIndex :: [a] -> [Int]
listIndex = delay 0

-- | Pair with list index.
pairPred :: [a] -> [(a, Int)]
pairPred xs = zip xs $ listIndex xs

-- | Different two list, and two lists must have same length.
-- otherwise throw a error.
different :: Ord a => [a] -> [a] -> [a]
different (x:xs) (y:ys) 
    | x == y    = different xs ys
    | otherwise = x : different xs ys
different [] [] = []
different _ _   = error "different: lists not the same length!"

-- | Do action when list not empty.
unlessNull :: [a] -> IO () -> IO ()
unlessNull = unless . null

-- | not . null
has :: [a] -> Bool
has = not . null

-- | Head monad list.
headM :: Monad m => m [a] -> m a
headM = liftM head

-- | Last monad list.
lastM :: Monad m => m [a] -> m a
lastM = liftM last

-- | ConcatM.
concatM :: Monad m => m [a] -> m [a] -> m [a]
concatM = liftM2 (++) 

-- | Replace n'th element (count from 0) in `xs` to `x` 
replaceAt :: Int -> [a] -> a -> [a]
replaceAt n xs x = take n xs ++ x : drop (n + 1) xs

-- | Split list with given condition.
splitWith :: (a -> Bool) -> [a] -> [[a]]
splitWith _ [] = []
splitWith f xs = fst tuple : splitWith f (dropWhile f $ snd tuple)
  where tuple = break f xs

-- | concatMapM.
concatMapM :: Monad m => (a -> m [b]) -> [a] -> m [b]
concatMapM f xs = liftM concat $ forM xs f

-- | Like find, but works with monadic computation instead of pure function.
-- In expression `find FUNCTION list`, if FUNCTION is "IO Bool", you can use
-- `findM FUNCTION list` to instead.
findM :: Monad m => (a -> m Bool) -> [a] -> m (Maybe a)
findM _ [] = return Nothing
findM f (x:xs) = ifM (f x) (return $ Just x) (findM f xs)

-- | Apply two monad function with list. 
-- And return new monad tuples list.
apply2M :: Monad m => [a] -> (a -> m b) -> (a -> m c) -> m [(b, c)]
apply2M xs f g = forM xs (\x -> zipM' (f x) (g x))

-- | Apply two function with list.
-- And return new tuples list.
apply2 :: [a] -> (a -> b) -> (a -> c) -> [(b, c)]
apply2 xs f g = map (f &&& g) xs

-- | Partition list.
partitionM :: Monad m => (a -> m Bool) -> [a] -> m ([a], [a])
partitionM _ [] = return ([], [])
partitionM f (x:xs) = 
    liftM2 mappend (ifM (f x) (return ([x], [])) (return ([], [x]))) (partitionM f xs)

-- | Like `init`, but accept empty list.
init_ [] = []
init_ xs = init xs

-- | Like `foldl1`, but accept empty list. 
foldl1_ _ [] = []
foldl1_ f xs = foldl1 f xs

-- | Find next element.
findNext :: (a -> Bool) -> [a] -> Maybe a
findNext x = listToMaybe . drop 1 . dropWhile (not . x)

-- | Find next cycle.
findNextCycle :: (a -> Bool) -> [a] -> Maybe a
findNextCycle _ []   = Nothing
findNextCycle f list = 
  case dropWhile (not . f) list of
    []  -> Nothing               -- `Nothing` when element not found in list
    [_] -> Just $ head list      -- Get head when element at last of list
    ls  -> listToMaybe $ drop 1 ls -- Otherwise get next element in list
        
-- | Find previous cycle.
findPrevCycle :: (a -> Bool) -> [a] -> Maybe a
findPrevCycle _ []   = Nothing
findPrevCycle f list 
    | length dropList == length list  
        = Nothing              -- `Nothing` when element not found in list
    | null dropList     
        = Just $ last list     -- Get last when element at first position of list
    | otherwise         
        = Just $ last dropList -- Otherwise get previous element in list
    where dropList = takeWhile (not . f) list

-- | Delete at.
deleteAt :: Int -> [a] -> [a]
deleteAt _ [] = []
deleteAt i xs
    | i < 0 
      = []
    | i >= length xs
      = []
    | otherwise 
      = (\(a, b) -> a ++ tail b) (splitAt i xs)

-- | Get last one.
getLast :: [a] -> Maybe a 
getLast [] = Nothing
getLast xs = Just $ last xs

-- | Get first one.
getFirst :: [a] -> Maybe a
getFirst [] = Nothing
getFirst xs = Just $ head xs

-- | Zip with list index.
zipWithIndex :: [a] -> (a -> Int -> c) -> [c]
zipWithIndex xs f = zipWith f xs [0..]

-- | Zip with list index.
zipWithIndexM :: Monad m => [a] -> (a -> Int -> m c) -> m [c]
zipWithIndexM xs f = zipWithM f xs [0..]

-- | Zip with list index.
zipWithIndexM_ :: Monad m => [a] -> (a -> Int -> m c) -> m ()
zipWithIndexM_ xs f = zipWithM_ f xs [0..]

-- | Like 'concatMap', but don't concat last one.
addMap :: ([a] -> [a]) -> [[a]] -> [a]
addMap _ []  = []
addMap _ [x] = x
addMap f xs  = concatMap f (init xs) ++ last xs

-- | Like 'insert' but just insert unique element.
insertUnique :: Ord a => a -> [a] -> [a]
insertUnique x xs 
    | x `elem` xs = xs
    | otherwise   = insert x xs