import Math.GenBase.Base (Fp (..), frombasef, zipAdd)
import Math.GenBase.Recur (Literal (..))
import Data.List (unfoldr)

zipUp  = foldl (flip (:))

--expand a 1 rightward
expand rule fp@(Fp (x:xs) a) = expand' [] xs
  where expand' zs [] = fp
        expand' zs (y:ys) | y > 0     = flip Fp a $ (x:) $ zipUp (y-1:zipAdd ys rule) zs
                          | otherwise = expand' (y:zs) ys

ruleMatch _    []  xs     = Just xs
ruleMatch True _   [x]    = Nothing
ruleMatch _    _   []     = Nothing --list consumed before rule
ruleMatch a (r:rs) (x:xs) | x < r     = Nothing
                          | otherwise = fmap (x-r:) $ ruleMatch a rs xs

--convert `from` something using the `rule` to `replace`
--`r` controls whether to ignore the last element of `xs`
--`Right` if no replacement took place; otherwise left
convert rule replace r from@(Fp xs x)
              | rule == fVect replace = Right from
              | otherwise             = convert' [] xs
  where convert' zs [] = Right from
        convert' zs as@(a:aas) = case ruleMatch r rule as of
          Nothing  -> convert' (a:zs) aas
          Just sum -> case null zs of
            --the rule matched in the first position
            True -> Left $ replace + (Fp sum x)
            _    -> Left $ flip Fp x $ zipUp (zipAdd frac sum) $ zipAdd zs $ reverse integ
        (integ, frac) = (\(Fp as a) -> splitAt a as) replace

convert' = (((either id id.).).) . convert

--generate recursive expansions from expanding/contracting a polynomial
tryExpand rule first = map (contract' False) $ unfoldr (Just . dupe . go) $ Fp first 0
  where --first pass: expand 1's rightward
        dupe a = (a,a)
        --check for occurrences of the string we started with; apply recursively
        selfcheck fp = convert' first fp False fp
        contract' = convert' rule $ Fp [1] 1
        go  = expand rule . selfcheck . contract' True

spacing = (1:) . spacing' 0
   where spacing' _ 0 = [1]
         spacing' x n = x:spacing' x (n-1)
isSpacing = (<=1) . length . dropWhile (==0) . tail . fVect

zeroRuns = zrs' [] 0
  where zrs' ys n [] = ys
        zrs' ys n (x:xs) | x == 0    = zrs' ys (n+1) xs
                         | otherwise = zrs' (n:ys) 0 xs
validSpacing n = (>n) . minimum . init . zeroRuns

--mutually recurse all spacings up to n, where `Fp (spacing n) 0 = Fp [1] 1`

--logic:
--start with list of spacings less than n
--for each current string representing spacing:
--  if maximally spaced, STOP
--  convert spacings, going from largest to smallest that are a substring in current (Left)
--  if no such spacing is found, test if this is a maximally spaced (Right)
--  otherwise, expand a 1 
--
--PROBLEMS: incapable of "looking past" a 1:bigrule, and gets caught in a 2-cycle
start n = map (tail . map (\(_,_,x) -> x)) $ iterate (\x -> map (advance $ each x) x) spacings
  where --generate all spacings 
        bigrule = spacing n
        zwei = (True, \x -> convert [2]     x False, Fp (1:bigrule) 0)
        eins = (True, \x -> convert bigrule x  True, Fp [1] 1)
        spacings = let builder = (\s -> (False, \x -> convert s x False, Fp s 0)) . spacing
          in zwei:eins:map builder [n-1,n-2..0]
        each = map (\(_,r,s) -> r s)

        hasMatch rule [] = False
        hasMatch rule xs = case ruleMatch False rule xs of
          Nothing -> hasMatch rule $ tail xs
          Just _  -> True

        advance current (p, rule, state)
                             | p         = (p, rule, state) --maximally spaced
                             | otherwise = either repackage testSpace foldRules
          --if any of these /succeeds/ then it's a Left and we exit early
          where foldRules = foldl (>>=) (Right state) $ advancement current
                advancement = case hasMatch (1:bigrule) $ fVect state of
                  True -> id --no match, goahead
                  _    -> id
                repackage fp = (p, rule, fp) --repackage any rules applied
              --otherwise no spacing was found
                testSpace fp | valid fp  = (True, rule, fp)
                             | otherwise = (False, rule, expand bigrule fp)
                valid = validSpacing n . fVect

--greedily search through the heads of the expansions, and see if
--one expansion can be used in another
start' n = map (map snd) $ iterate (\r -> map (continue r) r) $ map builder [0..n]
  where --generate all spacings 
        bigrule = spacing n
        --i COULD use a map, but nah
        builder m | n == m    = (True, Fp [1] 1)
                  | otherwise = (False, Fp (spacing m) 0)

        continue r (p,y) | p         = (True, y)
                         | otherwise = ruleSearch r y

        --lazy `(>m) . len`
        lenMore m _ []     = False
        lenMore m x (_:xs) | x < m     = lenMore m (x+1) xs
                           | otherwise = True

        ruleSearch rules fp@(Fp fs f) = rS' [] 0 fs
          where fpf             = flip Fp f
                rS' zs m []     = (True, fp)
                rS' zs m (x:xs) 
                      | x == 0           = rS' (x:zs) (m+1) xs
                      | x > 1            = (False, expand 1 $ map (*(x-1)) bigrule)
                      | m > n || null zs = rS' (x:zs) 0 xs
                      | null match = if worthwhile
                                     then (False, Fp (continue (2+r) rep) (f+r))
                                     else (False, expand (x-1) bigrule)
                      | otherwise  = (False, fpf $ zipUp (continue 2 frac) $ zipAdd match $ reverse integ)
                    where match         = drop (m+1) zs
                          (Fp rep r)    = snd $ rules !! m
                          (integ, frac) = splitAt r rep
                          worthwhile    = not (null integ) || lenMore (m+2) 0 frac
                          continue c    = zipAdd $ (replicate (m+c) 0) ++ xs
                          expand e      = fpf . flip zipUp zs . (e:) . zipAdd xs

--extract expansions for a single spacing `m` from `start n`
spacings n m = map (!!(n-m)) $ start n
--extract expansions for a single spacing `m` from `start' n`
spacings' n m = map (!!m) $ start' n

--sanity check: use approximate root to interpret expansion
sanityCheck f n m = let base = frombasef $ Literal $ spacing n
  in map (\x -> (x, base x)) $ f n m
