{-# LANGUAGE CPP #-}
{-# LANGUAGE TypeOperators, TypeSynonymInstances, FlexibleInstances, DefaultSignatures, UndecidableInstances #-}

{-| Various utils. Relic of the past, requires adaption to newer general purpose libs
 -}

module CHR.Utils
  (
  {-
    -- * Set
    unionMapSet

    -- * Map
  , inverseMap
  , showStringMapKeys
  
  , mapLookup2', mapLookup2
  
    -- * List
  , hdAndTl', hdAndTl
  -}
    maybeNull
  , maybeHd
  {-
  , wordsBy
  , initlast, initlast2
  , last'
  , firstNotEmpty
  , listSaturate, listSaturateWith
  , spanOnRest
  , filterMb
  -}
  , splitPlaces
  , combineToDistinguishedEltsBy
  {-
  , partitionOnSplit
  -}
  , zipWithN
  {-
  
    -- * Tuple
  , tup123to1, tup123to2
  , tup123to12, tup123to23
  , tup12to123
  
  , fst3
  , snd3
  , thd3
  , thd 
  
  , tup1234to1  
  , tup1234to2  
  , tup1234to3  
  , tup1234to4  
  
  , tup1234to12
  , tup1234to13
  , tup1234to14
  , tup1234to23
  , tup1234to24
  , tup1234to34
  
  , tup1234to123
  , tup1234to234
  
  , tup1234to124
  , tup1234to134
  
  , tup123to1234
 
  , fst4
  , snd4
  , thd4
  , fth4
  , fth 
  
    -- * String
  , strWhite
  , strPad
  , strCapitalize
  , strToLower
  , strToInt
  
  , splitForQualified
  
    -- * Show utils
  , showUnprefixedWithShowTypeable
  , DataAndConName(..)
  , showUnprefixed
  
    -- * Ordering
    -}
  , orderingLexic
  , orderingLexicList

  {-
    -- * Misc
  -}
  , panic
  , isSortedByOn
  , sortOnLazy
  , sortOn
  , sortByOn
  , groupOn
  , groupByOn
  , groupSortOn
  , groupSortByOn
  {-
  , nubOn
  
  , consecutiveBy
  
  , partitionAndRebuild
  -}
    -- * Maybe
  , panicJust
  {-
  , ($?)
  , orMb
  , maybeAnd
  , maybeOr
  
    -- * Graph
  -- , scc
  
    -- * Monad
  , firstMaybeM
  , breakM
  -}
  )
  where

import Data.Char
import Data.List
import Data.Maybe
import Data.Function
import Data.Typeable
import GHC.Generics
import qualified Data.Set as Set
import qualified Data.Map as Map
-- import qualified Data.Graph as Graph

{-

-------------------------------------------------------------------------
-- Set
-------------------------------------------------------------------------

-- | Union a set where each element itself is mapped to a set
unionMapSet :: Ord b => (a -> Set.Set b) -> (Set.Set a -> Set.Set b)
unionMapSet f = Set.unions . map f . Set.toList

-------------------------------------------------------------------------
-- Map
-------------------------------------------------------------------------

-- | Inverse of a map
inverseMap :: (Ord k, Ord v') => (k -> v -> (v',k')) -> Map.Map k v -> Map.Map v' k'
inverseMap mk = Map.fromList . map (uncurry mk) . Map.toList

-- | Show keys of map using a separator
showStringMapKeys :: Map.Map String x -> String -> String
showStringMapKeys m sep = concat $ intersperse sep $ Map.keys m
-}

-------------------------------------------------------------------------
-- List
-------------------------------------------------------------------------

{-
-- | Get head and tail, with default if empty list
hdAndTl' :: a -> [a] -> (a,[a])
hdAndTl' _ (a:as) = (a,as)
hdAndTl' n []     = (n,[])

-- | Get head and tail, with panic/error if empty list
hdAndTl :: [a] -> (a,[a])
hdAndTl = hdAndTl' (panic "hdAndTl")
{-# INLINE hdAndTl  #-}
-}

maybeNull :: r -> ([a] -> r) -> [a] -> r
maybeNull n f l = if null l then n else f l
{-# INLINE maybeNull  #-}

maybeHd :: r -> (a -> r) -> [a] -> r
maybeHd n f = maybeNull n (f . head)
{-# INLINE maybeHd  #-}

{-
-- | Split up in words by predicate
wordsBy :: (a -> Bool) -> [a] -> [[a]]
wordsBy p l
  = w l
  where w [] = []
        w l  = let (l',ls') = break p l
               in  l' : case ls' of []       -> []
                                    (_:[])   -> [[]]
                                    (_:ls'') -> w ls''

-- | Possibly last element and init
initlast :: [a] -> Maybe ([a],a)
initlast as
  = il [] as
  where il acc [a]    = Just (reverse acc,a)
        il acc (a:as) = il (a:acc) as
        il _   _      = Nothing

-- | variation on last which returns empty value instead of
last' :: a -> [a] -> a
last' e = maybe e snd . initlast

-- | Possibly last and preceding element and init
initlast2 :: [a] -> Maybe ([a],a,a)
initlast2 as
  = il [] as
  where il acc [a,b]  = Just (reverse acc,a,b)
        il acc (a:as) = il (a:acc) as
        il _   _      = Nothing

-- | First non empty list of list of lists
firstNotEmpty :: [[x]] -> [x]
firstNotEmpty = maybeHd [] id . filter (not . null)

-- | Saturate a list, that is:
-- for all indices i between min and max,
-- if there is no listelement x for which  get x  returns i,
-- add an element  mk i  to the list
listSaturate :: (Enum a,Ord a) => a -> a -> (x -> a) -> (a -> x) -> [x] -> [x]
listSaturate min max get mk xs
  = [ Map.findWithDefault (mk i) i mp | i <- [min..max] ]
  where mp = Map.fromList [ (get x,x) | x <- xs ]

-- | Saturate a list with values from assoc list, that is:
-- for all indices i between min and max,
-- if there is no listelement x for which  get x  returns i,
-- add a candidate from the associationlist (which must be present) to the list
listSaturateWith :: (Enum a,Ord a) => a -> a -> (x -> a) -> [(a,x)] -> [x] -> [x]
listSaturateWith min max get missing l
  = listSaturate min max get mk l
  where mp = Map.fromList missing
        mk a = panicJust "listSaturateWith" $ Map.lookup a mp

-- variant on span, predicate on full list
spanOnRest :: ([a] -> Bool) -> [a] -> ([a],[a])
spanOnRest p []       = ([],[])
spanOnRest p xs@(x:xs')
     | p xs      = (x:ys, zs)
     | otherwise = ([],xs)
                       where (ys,zs) = spanOnRest p xs'

-- | variant on 'filter', where predicate also yields a result
filterMb :: (a -> Maybe b) -> [a] -> [b]
filterMb p = catMaybes . map p
{-# INLINE filterMb #-}

-}

-- | Split at index places (inspired by/from split package). Places should be increasing, starting with an index >= 0.
-- The number of sublists returned is one higher than the number of places.
-- 
-- Examples:
-- >>> splitPlaces [2,3] [1,2,3,4,5,6,7] 
-- [[1,2],[3],[4,5,6,7]]
--
-- >>> splitPlaces [6,7] [1,2,3,4,5,6,7] 
-- [[1,2,3,4,5,6],[7],[]]
--
-- >>> splitPlaces [0,7] [1,2,3,4,5,6,7]
-- [[],[1,2,3,4,5,6,7],[]]
--
-- >>> splitPlaces [0,1,2,3,4,5,6,7] [1,2,3,4,5,6,7] 
-- [[],[1],[2],[3],[4],[5],[6],[7],[]]
splitPlaces
  :: [Int]            -- ^ places
  -> [e]
  -> [[e]]
splitPlaces ps es = spl 0 ps es
  where spl _   []     es = [es]
        spl pos (p:ps) es = es1 : spls
          where (es1,es2) = splitAt (p-pos) es
                spls = spl (pos + length es1) ps es2

-- | Combine [[x1..xn],..,[y1..ym]] to [[x1..y1],[x2..y1],..,[xn..ym]].
--   Each element [xi..yi] is distinct based on the the key k in xi==(k,_)
combineToDistinguishedEltsBy :: (e -> e -> Bool) -> [[e]] -> [[e]]
combineToDistinguishedEltsBy _  []     = []
combineToDistinguishedEltsBy _  [[]]   = []
combineToDistinguishedEltsBy _  [x]    = map (:[]) x
combineToDistinguishedEltsBy eq (l:ls)
  = combine l $ combineToDistinguishedEltsBy eq ls
  where combine l ls
          = concatMap (\e
                         -> mapMaybe (\ll -> maybe (Just (e:ll)) (const Nothing) $ find (eq e) ll)
                                     ls
                      ) l

zipWithN :: ([x] -> y) -> [[x]] -> [y]
zipWithN f l | any null l = []
             | otherwise  = f (map head l) : zipWithN f (map tail l)

{-

-------------------------------------------------------------------------
-- Tupling, untupling
-------------------------------------------------------------------------

tup123to1  (a,_,_) = a
tup123to2  (_,a,_) = a
tup123to3  (_,_,a) = a
{-# INLINE tup123to1  #-}
{-# INLINE tup123to2  #-}
{-# INLINE tup123to3  #-}

tup123to12 (a,b,_) = (a,b)
tup123to23 (_,a,b) = (a,b)
{-# INLINE tup123to12 #-}
{-# INLINE tup123to23 #-}

tup12to123 c (a,b) = (a,b,c)
{-# INLINE tup12to123 #-}

fst3 = tup123to1
snd3 = tup123to2
thd3 = tup123to3
thd  = thd3
{-# INLINE fst3 #-}
{-# INLINE snd3 #-}
{-# INLINE thd3 #-}
{-# INLINE thd  #-}

tup1234to1   (a,_,_,_) = a
tup1234to2   (_,a,_,_) = a
tup1234to3   (_,_,a,_) = a
tup1234to4   (_,_,_,a) = a
{-# INLINE tup1234to1   #-}
{-# INLINE tup1234to2   #-}
{-# INLINE tup1234to3   #-}
{-# INLINE tup1234to4   #-}

tup1234to12  (a,b,_,_) = (a,b)
tup1234to13  (a,_,b,_) = (a,b)
tup1234to14  (a,_,_,b) = (a,b)
tup1234to23  (_,a,b,_) = (a,b)
tup1234to24  (_,a,_,b) = (a,b)
tup1234to34  (_,_,a,b) = (a,b)
{-# INLINE tup1234to12 #-}
{-# INLINE tup1234to13 #-}
{-# INLINE tup1234to14 #-}
{-# INLINE tup1234to23 #-}
{-# INLINE tup1234to24 #-}
{-# INLINE tup1234to34 #-}

tup1234to123 (a,b,c,_) = (a,b,c)
tup1234to234 (_,a,b,c) = (a,b,c)
{-# INLINE tup1234to123 #-}
{-# INLINE tup1234to234 #-}

tup1234to124 (a,b,_,c) = (a,b,c)
tup1234to134 (a,_,b,c) = (a,b,c)
{-# INLINE tup1234to124 #-}
{-# INLINE tup1234to134 #-}

tup123to1234 d (a,b,c) = (a,b,c,d)
{-# INLINE tup123to1234 #-}

fst4 = tup1234to1
snd4 = tup1234to2
thd4 = tup1234to3
fth4 = tup1234to4
fth  = fth4
{-# INLINE fst4 #-}
{-# INLINE snd4 #-}
{-# INLINE thd4 #-}
{-# INLINE fth4 #-}
{-# INLINE fth  #-}


-------------------------------------------------------------------------
-- String
-------------------------------------------------------------------------

-- | Blanks
strWhite :: Int -> String
strWhite sz = replicate sz ' '
{-# INLINE strWhite #-}

-- | Pad upto size with blanks
strPad :: String -> Int -> String
strPad s sz = s ++ strWhite (sz - length s)

-- | Capitalize first letter
strCapitalize :: String -> String
strCapitalize s
  = case s of
      (c:cs) -> toUpper c : cs
      _      -> s

-- | Lower case
strToLower :: String -> String
strToLower = map toLower
{-# INLINE strToLower #-}

-- | Convert string to Int
strToInt :: String -> Int
strToInt = foldl (\i c -> i * 10 + ord c - ord '0') 0

-------------------------------------------------------------------------
-- Split for qualified name
-------------------------------------------------------------------------

-- | Show, additionally removing type name prefix, assuming constructor names are prefixed with type name, possibly with additional underscore (or something like that)
showUnprefixedWithShowTypeable :: (Show x, Typeable x) => Int -> x -> String
showUnprefixedWithShowTypeable extralen x = drop prelen $ show x
  where prelen = (length $ show $ typeOf x) + extralen

-- | Generic constructor name, to be used by show variations
class GDataAndConName f where
  gDataAndConName :: f x -> (String,String)

class DataAndConName x where
  -- | Get datatype and constructor name for a datatype
  dataAndConName :: x -> (String,String)
  
  default dataAndConName :: (Generic x, GDataAndConName (Rep x)) => x -> (String,String)
  dataAndConName = gDataAndConName . from

instance (Datatype d, GDataAndConName x) => GDataAndConName (D1 d x) where
  gDataAndConName d@(M1 x) = let (_,c) = gDataAndConName x in (datatypeName d, c)

instance (GDataAndConName a, GDataAndConName b) => GDataAndConName (a :+: b) where
  gDataAndConName (L1 x) = gDataAndConName x
  gDataAndConName (R1 x) = gDataAndConName x

instance (Constructor c) => GDataAndConName (C1 c x) where
  gDataAndConName c = ("", conName c)

-- | Show, additionally removing type name prefix, assuming constructor names are prefixed with type name, possibly with additional underscore (or something like that)
showUnprefixed :: (DataAndConName x) => Int -> x -> String
showUnprefixed extralen x = drop prelen $ c
  where (d,c) = dataAndConName x
        prelen = (length d) + extralen

-------------------------------------------------------------------------
-- Split for qualified name
-------------------------------------------------------------------------

-- | Split into fragments based on '.' convention for qualified Haskell names
splitForQualified :: String -> [String]
splitForQualified s
    = ws''
    where ws  = wordsBy (=='.') s
          ws' = case initlast2 ws of
                  Just (ns,n,"") -> ns ++ [n ++ "."]
                  _              -> ws
          ws''= case break (=="") ws' of
                  (nq,(_:ns)) -> nq ++ [concatMap ("."++) ns]
                  _ -> ws'

-}


-------------------------------------------------------------------------
-- Misc
-------------------------------------------------------------------------

-- | Error, with message
panic m = error ("panic: " ++ m)

-------------------------------------------------------------------------
-- group/sort/nub combi's
-------------------------------------------------------------------------

isSortedByOn :: (b -> b -> Ordering) -> (a -> b) -> [a] -> Bool
isSortedByOn cmp sel l
  = isSrt l
  where isSrt (x1:tl@(x2:_)) = cmp (sel x1) (sel x2) /= GT && isSrt tl
        isSrt _              = True

-- | A slightly more lazy version of Data.List.sortOn.
-- See also https://github.com/UU-ComputerScience/uhc-util/issues/5 .
sortOnLazy :: Ord b => (a -> b) -> [a] -> [a]
sortOnLazy = sortByOn compare
{-# INLINE sortOnLazy #-}

#if __GLASGOW_HASKELL__ >= 710
#else
-- | The original Data.List.sortOn.
-- See also https://github.com/UU-ComputerScience/uhc-util/issues/5 .
sortOn :: Ord b => (a -> b) -> [a] -> [a]
sortOn = sortOnLazy
{-# INLINE sortOn #-}
#endif


sortByOn :: (b -> b -> Ordering) -> (a -> b) -> [a] -> [a]
sortByOn cmp sel = sortBy (cmp `on` sel) -- (\e1 e2 -> sel e1 `cmp` sel e2)

groupOn :: Eq b => (a -> b) -> [a] -> [[a]]
groupOn sel = groupBy ((==) `on` sel) -- (\e1 e2 -> sel e1 == sel e2)

groupSortOn :: Ord b => (a -> b) -> [a] -> [[a]]
groupSortOn sel = groupOn sel . sortOn sel

groupByOn :: (b -> b -> Bool) -> (a -> b) -> [a] -> [[a]]
groupByOn eq sel = groupBy (eq `on` sel) -- (\e1 e2 -> sel e1 `eq` sel e2)

groupSortByOn :: (b -> b -> Ordering) -> (a -> b) -> [a] -> [[a]]
groupSortByOn cmp sel = groupByOn (\e1 e2 -> cmp e1 e2 == EQ) sel . sortByOn cmp sel

{-
nubOn :: Eq b => (a->b) -> [a] -> [a]
nubOn sel = nubBy ((==) `on` sel) -- (\a1 a2 -> sel a1 == sel a2)

-- | The 'consecutiveBy' function groups like groupBy, but based on a function which says whether 2 elements are consecutive
consecutiveBy                  :: (a -> a -> Bool) -> [a] -> [[a]]
consecutiveBy _        []      =  []
consecutiveBy isConsec (x:xs)  =  ys : consecutiveBy isConsec zs
  where (ys,zs) = consec x xs
        consec x []                        = ([x],[])
        consec x yys@(y:ys) | isConsec x y = let (yys',zs) = consec y ys in (x:yys',zs)
                            | otherwise    = ([x],yys)

-- | Partition on part of something, yielding a something else in the partitioning
partitionOnSplit :: (a -> (x,y)) -> (x -> x') -> (x -> Bool) -> [a] -> ([(x',y)],[y])
partitionOnSplit split adapt pred xs = foldr sel ([],[]) xs
  where sel x ~(ts,fs) | pred x'   = ((adapt x',y):ts,   fs)
                       | otherwise = (             ts, y:fs)
          where (x',y) = split x
-}

{-
partition               :: (a -> Bool) -> [a] -> ([a],[a])
{-# INLINE partition #-}
partition p xs = foldr (select p) ([],[]) xs

select :: (a -> Bool) -> a -> ([a], [a]) -> ([a], [a])
select p x ~(ts,fs) | p x       = (x:ts,fs)
                    | otherwise = (ts, x:fs)
-}

{-
-------------------------------------------------------------------------
-- Partitioning with rebuild
-------------------------------------------------------------------------

-- | Partition, but also return a function which will rebuild according to the original ordering of list elements
partitionAndRebuild :: (v -> Bool) -> [v] -> ([v], [v], [v'] -> [v'] -> [v'])
partitionAndRebuild f (v:vs)
  | f v                  = (v : vs1,     vs2, \(r:r1)   r2  -> r : mk r1 r2)
  | otherwise            = (    vs1, v : vs2, \   r1 (r:r2) -> r : mk r1 r2)
  where (vs1,vs2,mk) = partitionAndRebuild f vs
partitionAndRebuild _ [] = ([], [], \_ _ -> [])

-}

-------------------------------------------------------------------------
-- Ordering
-------------------------------------------------------------------------

-- | Reduce compare results lexicographically to one compare result
orderingLexicList :: [Ordering] -> Ordering
orderingLexicList = foldr1 orderingLexic
{-# INLINE orderingLexicList #-}

-- | Reduce compare results lexicographically using a continuation ordering
orderingLexic :: Ordering -> Ordering -> Ordering
orderingLexic o1 o2 = if o1 == EQ then o2 else o1
{-# INLINE orderingLexic #-}


-------------------------------------------------------------------------
-- Maybe
-------------------------------------------------------------------------


panicJust :: String -> Maybe a -> a
panicJust m = maybe (panic m) id
{-# INLINE panicJust #-}

{-
infixr 0  $?

($?) :: (a -> Maybe b) -> Maybe a -> Maybe b
f $? mx = do x <- mx
             f x

orMb :: Maybe a -> Maybe a -> Maybe a
orMb m1 m2 = maybe m2 (const m1) m1
-- orMb = maybeOr Nothing Just Just

maybeAnd :: x -> (a -> b -> x) -> Maybe a -> Maybe b -> x
maybeAnd n jj ma mb
  = case ma of
      Just a
        -> case mb of {Just b -> jj a b ; _ -> n}
      _ -> n

maybeOr :: x -> (a -> x) -> (b -> x) -> Maybe a -> Maybe b -> x
maybeOr n fa fb ma mb
  = case ma of
      Just a -> fa a
      _      -> case mb of
                  Just b -> fb b
                  _      -> n

-}

-------------------------------------------------------------------------
-- Strongly Connected Components
-------------------------------------------------------------------------

{-
scc :: Ord n => [(n,[n])] -> [[n]]
scc = map Graph.flattenSCC . Graph.stronglyConnComp . map (\(n,ns) -> (n, n, ns))
-}

{-
-------------------------------------------------------------------------
-- Map
-------------------------------------------------------------------------

-- | double lookup, with transformer for 2nd map
mapLookup2' :: (Ord k1, Ord k2) => (v1 -> Map.Map k2 v2) -> k1 -> k2 -> Map.Map k1 v1 -> Maybe (Map.Map k2 v2, v2)
mapLookup2' f k1 k2 m1
  = do m2 <- Map.lookup k1 m1
       let m2' = f m2
       fmap ((,) m2') $ Map.lookup k2 m2'

-- | double lookup
mapLookup2 :: (Ord k1, Ord k2) => k1 -> k2 -> Map.Map k1 (Map.Map k2 v2) -> Maybe v2
mapLookup2 k1 k2 m1 = fmap snd $ mapLookup2' id k1 k2 m1
{-# INLINE mapLookup2 #-}

-------------------------------------------------------------------------
-- Monad
-------------------------------------------------------------------------

-- | loop over monads yielding a Maybe from a start value, yielding the first Just or the start (when no Just is returned)
firstMaybeM :: Monad m => a -> [a -> m (Maybe a)] -> m a
firstMaybeM x []     = return x
firstMaybeM x (s:ss) = do mx <- s x
                          maybe (firstMaybeM x ss) return mx

-- | Monadic equivalent of break: evaluate monads until a predicate is True, returning what is yes/no evaluated and the split point
breakM :: Monad m => (a -> Bool) -> [m a] -> m ([a], Maybe (a,[m a]))
breakM p l = br [] l >>= \(acc,res) -> return (reverse acc, res)
  where br acc []     = return (acc, Nothing)
        br acc (m:ms) = m >>= \x -> if p x then return (acc, Just (x, ms)) else br (x:acc) ms

-}