{-# LANGUAGE MultiParamTypeClasses, GeneralizedNewtypeDeriving, DeriveDataTypeable #-}

module Development.Shake.FilePattern(
    FilePattern, (?==),
    compatible, extract, substitute
    ) where

import Data.List


-- | A type synonym for file patterns, containing @\/\/@ and @*@. For the syntax
--   and semantics of 'FilePattern' see '?=='.
type FilePattern = String


-- | Match a 'FilePattern' against a 'FilePath', There are only two special forms:
--
-- * @*@ matches an entire path component, excluding any separators.
--
-- * @\/\/@ matches an arbitrary number of path componenets.
--
--   Some examples that match:
--
-- > "//*.c" ?== "foo/bar/baz.c"
-- > "*.c" ?== "baz.c"
-- > "//*.c" ?== "baz.c"
-- > "test.c" ?== "test.c"
--
--   Examples that /don't/ match:
--
-- > "*.c" ?== "foo/bar.c"
-- > "*/*.c" ?== "foo/bar/baz.c"
--
(?==) :: FilePattern -> FilePath -> Bool
(?==) ('/':'/':p) x = any (p ?==) $ x : [i | '/':i <- tails x]
(?==) ('*':p) x = any (p ?==) $ a ++ take 1 b
    where (a,b) = break ("/" `isPrefixOf`) $ tails x
(?==) (p:ps) (x:xs) | p == x = ps ?== xs
(?==) [] [] = True
(?==) _ _ = False


-- | Do they have the same * and // counts in the same order
compatible :: [FilePattern] -> Bool
compatible [] = True
compatible (x:xs) = all ((==) (f x) . f) xs
    where
        f ('*':xs) = '*':f xs
        f ('/':'/':xs) = '/':f xs
        f (x:xs) = f xs
        f [] = []


-- | Extract the items that match the wildcards. The pair must match with '?=='.
extract :: FilePattern -> FilePath -> [String]
extract p x = head $ f p x ++ [[]]
    where
        f ('/':'/':p) x = rest p $ ("",x) : [(pre++"/",i) | (pre,'/':i) <- zip (inits x) (tails x)]
        f ('*':p) x = rest p $ a ++ take 1 b
            where (a,b) = break (isPrefixOf "/" . snd) $ zip (inits x) (tails x)
        f (p:ps) (x:xs) | p == x = f ps xs
        f [] [] = [[]]
        f _ _ = []

        rest p xs = [skip:res | (skip,keep) <- xs, res <- f p keep]


-- | Given the result of 'extract', substitute it back in to a 'compatible' pattern.
--
-- > p '?==' x ==> substitute (extract p x) p == x
substitute :: [String] -> FilePattern -> FilePath
substitute = f
    where
        f (a:as) ('/':'/':ps) = a ++ f as ps
        f (a:as) ('*':ps) = a ++ f as ps
        f as (p:ps) = p : f as ps
        f as [] = []