{-# LANGUAGE ViewPatterns, DeriveFunctor, BangPatterns, TupleSections, RecordWildCards #-}
module System.FilePattern.Step(
    step, step_, Step(..), StepNext(..)
    ) where
import System.FilePattern.Core
import System.FilePattern.Tree
import System.FilePattern.Wildcard
import Control.Monad.Extra
import Data.List.Extra
import Data.Semigroup
import Data.Tuple.Extra
import Data.Functor
import Data.Either
import qualified Data.List.NonEmpty as NE
import Prelude
data StepNext
    =
      
      
      
      StepOnly [String]
    | 
      StepEverything
    | 
      StepUnknown
      deriving (Eq,Ord,Show)
mergeStepNext :: [StepNext] -> StepNext
mergeStepNext = f id
    where
        f rest [] = StepOnly $ rest []
        f rest (StepUnknown:xs) = if StepEverything `elem` xs then StepEverything else StepUnknown
        f rest (StepEverything:xs) = StepEverything
        f rest (StepOnly x:xs) = f (rest . (x ++)) xs
normaliseStepNext :: StepNext -> StepNext
normaliseStepNext (StepOnly xs) = StepOnly $ nubOrd xs
normaliseStepNext x = x
instance Semigroup StepNext where
    a <> b = sconcat $ NE.fromList [a,b]
    sconcat = normaliseStepNext . mergeStepNext . NE.toList
instance Monoid StepNext where
    mempty = StepOnly []
    mappend = (<>)
    mconcat = maybe mempty sconcat . NE.nonEmpty 
data Step a = Step
    {stepDone :: [(a, [String])]
        
        
    ,stepNext :: StepNext
        
    ,stepApply :: String -> Step a
        
    }
    deriving Functor
mergeStep :: (StepNext -> StepNext) -> [Step a] -> Step a
mergeStep f [] = mempty
mergeStep f [x] = x
mergeStep f xs = Step
    {stepDone = concatMap stepDone xs
    ,stepNext = f $ mergeStepNext $ map stepNext xs
    ,stepApply = \x -> mergeStep f $ map (`stepApply` x) xs
    }
instance Semigroup (Step a) where
    a <> b = sconcat $ NE.fromList [a,b]
    sconcat (NE.toList -> ss)
        | [s] <- ss = s
        | otherwise = Step
            {stepDone = concatMap stepDone ss
            ,stepNext = mconcat $ map stepNext ss
            ,stepApply = \x -> fastFoldMap (`stepApply` x) ss
            }
instance Monoid (Step a) where
    mempty = Step [] mempty $ const mempty
    mappend = (<>)
    mconcat = maybe mempty sconcat . NE.nonEmpty 
fastFoldMap :: Monoid m => (a -> m) -> [a] -> m
fastFoldMap f = mconcat . map f 
data Pat = Lits [Wildcard String]
         | StarStar
         | End
           deriving (Show,Eq,Ord)
toPat :: Pattern -> [Pat]
toPat (Pattern (Literal xs)) = [Lits xs]
toPat (Pattern (Wildcard pre mid post)) = intercalate [StarStar] $ map lit $ pre : mid ++ [post]
    where lit xs = [Lits xs | xs /= []]
step :: [(a, FilePattern)] -> Step a
step = restore . ($ id) . f [] . makeTree . map (second $ toPat . parsePattern)
    where
        f :: [Pat] -> Tree Pat a -> (Parts -> Step [a])
        f seen (Tree ends nxts) = \parts -> mergeStep id $ map ($ parts) $ sEnds ++ sNxts
            where
                sEnds = case unroll ends (seen ++ [End]) of
                    _ | null ends -> []
                    Just ([], c) -> [c (error "step invariant violated (1)")]
                    _ -> error $ "step invariant violated (2), " ++ show seen
                sNxts = flip map nxts $ \(p,ps) ->
                    let seen2 = seen ++ [p] in
                    case unroll (error "step invariant violated (3)") seen2 of
                        Nothing -> f seen2 ps
                        Just (nxt, c) -> c (f [] $ retree nxt ps)
        retree [] t = t
        retree (p:ps) t = Tree [] [(p, retree ps t)]
        restore :: Step [a] -> Step a 
        restore Step{..} = Step
            {stepDone = [(a, b) | (as,b) <- stepDone, a <- as]
            ,stepNext = normaliseStepNext stepNext
            ,stepApply = restore . stepApply
            }
step_ :: [FilePattern] -> Step ()
step_ = step . map ((),)
match1 :: Wildcard String -> String -> Maybe [String]
match1 w x = rights <$> wildcardMatch equals w x
type Parts = [String] -> [String]
unroll :: a -> [Pat] -> Maybe ([Pat], (Parts -> Step a) -> Parts -> Step a)
unroll val [End] = Just ([], \_ parts -> mempty{stepDone = [(val, parts [])]})
unroll val [StarStar,StarStar] = Just ([StarStar], \cont parts -> cont (parts . ([]:)))
unroll val [Lits (l:ls)] = Just ([Lits ls | ls /= []], \cont parts -> Step
    {stepDone = []
    ,stepNext = case l of Literal v -> StepOnly [v]; Wildcard{} -> StepUnknown
    ,stepApply = \s -> case match1 l s of
        Just xs -> cont (parts . (xs++))
        Nothing -> mempty
    })
unroll val [StarStar,End] = Just ([], \_ parts -> g parts [])
    where
        g parts rseen = Step
            {stepDone = [(val, parts [mkParts $ reverse rseen])]
            ,stepNext = StepEverything
            ,stepApply = \s -> g parts (s:rseen)
            }
unroll val [StarStar,Lits (reverse &&& length -> (rls,nls)),End] = Just ([], \_ parts -> g parts 0 [])
    where
        g parts !nseen rseen = Step
            {stepDone = case zipWithM match1 rls rseen of
                _ | nseen < nls -> [] 
                Just xss -> [(val, parts $ mkParts (reverse $ drop nls rseen) : concat (reverse xss))]
                Nothing -> []
            ,stepNext = StepUnknown
            ,stepApply = \s -> g parts (nseen+1) (s:rseen)
            }
unroll val [StarStar,Lits [l],StarStar] = Just ([StarStar], \cont parts -> g cont parts [])
    where
        g cont parts rseen = Step
            {stepDone = []
            ,stepNext = StepUnknown
            ,stepApply = \s -> case match1 l s of
                Just xs -> cont (parts . (++) (mkParts (reverse rseen) : xs))
                Nothing -> g cont parts (s:rseen)
            }
unroll val [StarStar,Lits (reverse &&& length -> (rls,nls)),StarStar] = Just ([StarStar], \cont parts -> g cont parts 0 [])
    where
        g cont parts !nseen rseen = Step
            {stepDone = []
            ,stepNext = StepUnknown
            ,stepApply = \s -> case zipWithM match1 rls (s:rseen) of
                _ | nseen+1 < nls -> g cont parts (nseen+1) (s:rseen) 
                Nothing -> g cont parts (nseen+1) (s:rseen)
                Just xss -> cont (parts . (++) (mkParts (reverse $ drop nls $ s:rseen) : concat (reverse xss)))
            }
unroll _ _ = Nothing