{-# LANGUAGE TupleSections, NoMonomorphismRestriction #-}
module Math.CommutativeAlgebra.GroebnerBasis where
import Data.List as L
import qualified Data.IntMap as IM
import qualified Data.Set as S
import Math.Core.Utils
import Math.Core.Field
import Math.Algebras.VectorSpace
import Math.Algebras.Structures
import Math.CommutativeAlgebra.Polynomial
sPoly f g = let d = tgcd (lt f) (lt g)
            in (lt g `tdiv` d) *-> f - (lt f `tdiv` d) *-> g
isGB fs = all (\h -> h %% fs == 0) (pairWith sPoly fs)
gb1 fs = gb' fs (pairWith sPoly fs) where
    gb' gs (h:hs) = let h' = h %% gs in
                    if h' == 0 then gb' gs hs else gb' (h':gs) (hs ++ map (sPoly h') gs)
    gb' gs [] = gs
pairWith f (x:xs) = map (f x) xs ++ pairWith f xs
pairWith _ [] = []
reduce gs = reduce' gs [] where
    reduce' (l:ls) rs = let l' = l %% (rs ++ ls) in
                        if l' == 0 then reduce' ls rs else reduce' ls (toMonic l':rs)
    reduce' [] rs = L.sort rs
gb2 fs = reduce $ gb' fs (pairs [1..s]) s where
    s = length fs
    gb' gs ((i,j):ps) t =
        let fi = gs!i; fj = gs!j in
        if mcoprime (lm fi) (lm fj) || criterion fi fj
        then gb' gs ps t
        else let h = sPoly fi fj %% gs in
             if h == 0 then gb' gs ps t else gb' (gs++[h]) (ps ++ [ (i,t+1) | i <- [1..t] ]) (t+1)
        where
            criterion fi fj = let l = mlcm (lm fi) (lm fj) in any (test l) [1..t]
            test l k = k `notElem` [i,j]
                    && ordpair i k `notElem` ps
                    && ordpair j k `notElem` ps
                    && lm (gs!k) `mdivides` l
    gb' gs [] _ = gs
xs ! i = xs !! (i-1) 
gb2a fs = reduce $ gb' fs' (pairs [1..s]) s
    where fs' = IM.fromList $ zip [1..] $ filter (/= 0) fs
          s = IM.size fs'
          gb' gs ((i,j):ps) t =
              let fi = gs IM.! i; fj = gs IM.! j in
              if mcoprime (lm fi) (lm fj) || criterion fi fj
              then gb' gs ps t
              else let h = sPoly fi fj %% IM.elems gs in
                   if h == 0
                   then gb' gs ps t
                   else let t' = t+1
                            gs' = IM.insert t' h gs
                            ps' = ps ++ map (,t') [1..t]
                        in gb' gs' ps' t'
              where criterion fi fj = let l = mlcm (lm fi) (lm fj) in any (test l) [1..t]
                    test l k = k `notElem` [i,j]
                            && ordpair i k `notElem` ps
                            && ordpair j k `notElem` ps
                            && lm (gs IM.! k) `mdivides` l
          gb' gs [] _ = IM.elems gs
gb3 fs = reduce $ gb1' [] fs [] 0
    where
    gb1' gs (f:fs) ps t = let ps' = updatePairs gs ps f t
                          in gb1' (gs ++ [f]) fs ps' (t+1)
    gb1' ls [] ps t = gb2' ls ps t
    gb2' gs ((i,j):ps) t =
        let h = sPoly (gs!i) (gs!j) %% gs in
        if h == 0
        then gb2' gs ps t
        else let ps' = updatePairs gs ((i,j):ps) h t in gb2' (gs++[h]) ps' (t+1)
    gb2' gs [] _ = gs
    updatePairs gs ps f t =
        [p | p@(i,j) <- ps,
             not (lm f `mdivides` mlcm (lm (gs!i)) (lm (gs!j)))]
     ++ [ (i,t+1) | (gi,i) <- zip gs [1..t],
                    not (mcoprime (lm gi) (lm f)),
                    not (criterion (mlcm (lm gi) (lm f)) i) ]
        where criterion l i = any (`mdivides` l) [lm gk | (gk,k) <- zip gs [1..t], k /= i, ordpair i k `notElem` ps]
gb4 fs = reduce $ gb1' [] fs' [] 0
    where fs' = reverse $ L.sort $ filter (/=0) fs
          gb1' gs (f:fs) ps t = gb1' (gs ++ [f]) fs ps' (t+1)
              where ps' = updatePairs gs ps f t
          gb1' ls [] ps t = gb2' ls ps t
          gb2' gs ((l,(i,j)):ps) t =
              let h = sPoly (gs!i) (gs!j) %% gs in
              if h == 0
              then gb2' gs ps t
              else let ps' = updatePairs gs ((l,(i,j)):ps) h t in gb2' (gs++[h]) ps' (t+1)
          gb2' gs [] _ = gs
          updatePairs gs ps f t =
              let oldps = [p | p@(l,(i,j)) <- ps, not (lm f `mdivides` l)]
                  newps = sortBy (flip cmpfst)
                          [ (l,(i,t+1)) | (gi,i) <- zip gs [1..t], let l = mlcm (lm gi) (lm f),
                                          not (mcoprime (lm gi) (lm f)),
                                          not (criterion l i) ]
              in mergeBy (flip cmpfst) oldps newps
              where criterion l i = any (`mdivides` l) [lm gk | (gk,k) <- zip gs [1..t], k /= i, ordpair i k `notElem` map snd ps]
mergeBy cmp (t:ts) (u:us) =
    case cmp t u of
    LT -> t : mergeBy cmp ts (u:us)
    EQ -> t : mergeBy cmp ts (u:us)
    GT -> u : mergeBy cmp (t:ts) us
mergeBy _ ts us = ts ++ us 
gb :: (Fractional k, Ord k, Monomial m, Ord m, Algebra k m) =>
    [Vect k m] -> [Vect k m]
gb fs =
    let fs' = reverse $ sort $ map toMonic $ filter (/=0) fs
    in reduce $ gb1' [] fs' [] 0 where
    gb1' gs (f:fs) ps t = gb1' (gs ++ [f]) fs ps' (t+1)
        where ps' = updatePairs gs ps f (t+1)
    gb1' ls [] ps t = gb2' ls ps t
    gb2' gs (p@(_,(i,j)):ps) t =
        if h == 0
        then gb2' gs ps t
        else gb2' (gs++[h]) ps' (t+1)
        where h = toMonic $ sPoly (gs!i) (gs!j) %% gs
              ps' = updatePairs gs (p:ps) h (t+1)
    gb2' gs [] _ = gs
    updatePairs gs ps gk k =
        let newps = [let l = mlcm (lm gi) (lm gk) in ((sugar gi gk l, l), (i,k)) | (gi,i) <- zip gs [1..k-1] ]
            ps' = [p | p@((sij,tij),(i,j)) <- ps,
                       let ((sik,tik),_) = newps ! i, let ((sjk,tjk),_) = newps ! j,
                       not ( (tik `mproperlydivides` tij) && (tjk `mproperlydivides` tij) ) ] 
            newps' = discard1 [] newps
            newps'' = sortBy (flip cmpSug) $ discard2 [] $ sortBy (flip cmpNormal) newps'
        in mergeBy (flip cmpSug) ps' newps''
        where
            discard1 ls (r@((_sik,tik),(i,_k)):rs) =
                if lm (gs!i) `mcoprime` lm gk
                
                then discard1 (filter (\((_,tjk),_) -> tjk /= tik) ls) (filter (\((_,tjk),_) -> tjk /= tik) rs)
                else discard1 (r:ls) rs
            discard1 ls [] = ls
            discard2 ls (r@((_sik,tik),(i,k)):rs) = discard2 (r:ls) $ filter (\((_sjk,tjk),(j,k')) -> not (k == k' && tik `mdivides` tjk)) rs
            discard2 ls [] = ls
sugar f g h = mdeg h + max (deg f - mdeg (lm f)) (deg g - mdeg (lm g))
cmpNormal ((s1,t1),(i1,j1)) ((s2,t2),(i2,j2)) = compare (t1,j1) (t2,j2)
cmpSug ((s1,t1),(i1,j1)) ((s2,t2),(i2,j2)) = compare (-s1,t1,j1) (-s2,t2,j2)
memberGB f gs = f %% gs == 0
memberI :: (Fractional k, Ord k, Monomial m, Ord m, Algebra k m) =>
    Vect k m -> [Vect k m] -> Bool
memberI f gs = memberGB f (gb gs)
sumI :: (Fractional k, Ord k, Monomial m, Ord m, Algebra k m) =>
     [Vect k m] -> [Vect k m] -> [Vect k m]
sumI fs gs = gb (fs ++ gs)
productI :: (Fractional k, Ord k, Monomial m, Ord m, Algebra k m) =>
     [Vect k m] -> [Vect k m] -> [Vect k m]
productI fs gs = gb [f * g | f <- fs, g <- gs]
intersectI :: (Fractional k, Ord k, Monomial m, Ord m) =>
     [Vect k m] -> [Vect k m] -> [Vect k m]
intersectI fs gs =
    let t = toElimFst $ return $ (mvar "t" :: Glex String)
        hs = map ((t *) . toElimSnd) fs ++ map (((1-t) *) . toElimSnd) gs
    in eliminateFst hs
toElimFst = fmap (\m -> Elim2 m munit)
toElimSnd = fmap (\m -> Elim2 munit m)
isElimFst = (/= munit) . (\(Elim2 m _) -> m) . lm
fromElimSnd = fmap (\(Elim2 _ m) -> m)
eliminateFst = map fromElimSnd . dropWhile isElimFst . gb
quotientI :: (Fractional k, Ord k, Monomial m, Ord m, Algebra k m) =>
    [Vect k m] -> [Vect k m] -> [Vect k m]
quotientI _ [] = [1]
quotientI fs gs = foldl1 intersectI $ map (quotientP fs) gs
quotientP fs g = map ( // g ) $ intersectI fs [g]
    where h // g = let ([u],_) = quotRemMP h [g] in u
eliminate :: (Eq k, Fractional k, Ord k, MonomialConstructor m, Monomial (m v), Ord (m v)) =>
    [Vect k (m v)] -> [Vect k (m v)] -> [Vect k (m v)]
eliminate vs gs = let subs = subFst vs in eliminateFst [g `bind` subs | g <- gs]
    where subFst :: (Eq k, Num k, MonomialConstructor m, Eq (m v), Mon (m v)) =>
              [Vect k (m v)] -> v -> Vect k (Elim2 (m v) (m v))
          subFst vs = (\v -> let v' = var v in if v' `elem` vs then toElimFst v' else toElimSnd v')
mbasis vs = mbasis' [1]
    where mbasis' ms = ms ++ mbasis' (toSet [v*m | v <- vs, m <- ms])
mbasisQA :: (Fractional k, Ord k, Monomial m, Ord m, Algebra k m) =>
     [Vect k m] -> [Vect k m] -> [Vect k m]
mbasisQA vs gs = mbasisQA' [1]
    where mbasisQA' [] = []  
          mbasisQA' ms = ms ++ mbasisQA' (toSet [f | v <- vs, m <- ms, let f = v*m, f %% gs == f])
ltIdeal :: (Fractional k, Ord k, Monomial m, Ord m, Algebra k m) =>
    [Vect k m] -> [Vect k m]
ltIdeal gs = map (return . lm) $ gb gs
numMonomials n i = toInteger (i+n-1) `choose` toInteger (n-1)
hilbertFunQA :: (Fractional k, Ord k, Monomial m, Ord m, Algebra k m) =>
    [Vect k m] -> [Vect k m] -> Int -> Integer
hilbertFunQA vs gs i = hilbertFunQA' (ltIdeal gs) i
    where n = length vs
          hilbertFunQA' _ i | i < 0 = 0
          hilbertFunQA' (m:ms) i = hilbertFunQA' ms i - hilbertFunQA' (ms `quotientP` m) (i - deg m)
          hilbertFunQA' [] i = numMonomials n i
hilbertSeriesQA1 vs gs = hilbertSeriesQA1' [1]
    where hilbertSeriesQA1' [] = [] 
          hilbertSeriesQA1' ms = length ms : hilbertSeriesQA1' (toSet [f | v <- vs, m <- ms, let f = v*m, f %% gs == f])
hilbertSeriesQA :: (Fractional k, Ord k, Monomial m, Ord m, Algebra k m) =>
    [Vect k m] -> [Vect k m] -> [Integer]
hilbertSeriesQA vs gs = hilbertSeriesQA' $ ltIdeal gs
    where hilbertSeriesQA' (m:ms) = hilbertSeriesQA' ms <-> (replicate (deg m) 0 ++ hilbertSeriesQA' (ms `quotientI` [m]))
          hilbertSeriesQA' [] = [numMonomials n i | i <- [0..] ]
          n = length vs
          (a:as) <-> (b:bs) = (a-b) : (as <-> bs)
          as <-> [] = as
          [] <-> bs = map negate bs
hilbertSeriesQA' :: (Fractional k, Ord k, MonomialConstructor m, Ord (m v), Monomial (m v), Algebra k (m v)) =>
    [Vect k (m v)] -> [Integer]
hilbertSeriesQA' gs = hilbertSeriesQA vs gs where vs = toSet (concatMap vars gs)
hilbertPolyQA :: (Fractional k, Ord k, Monomial m, Ord m, Algebra k m) =>
     [Vect k m] -> [Vect k m] -> GlexPoly Q String
hilbertPolyQA vs gs = hilbertPolyQA' (ltIdeal gs) i
    where n = toInteger $ length vs
          i = glexvar "i"
          hilbertPolyQA' [] x = product [ x + fromInteger j | j <- [1..n-1] ] / (fromInteger $ product [1..n-1])
          hilbertPolyQA' (m:ms) x = hilbertPolyQA' ms x - hilbertPolyQA' (ms `quotientP` m) (x - fromIntegral (deg m))
hilbertPolyQA' :: (Fractional k, Ord k, MonomialConstructor m, Ord (m v), Monomial (m v), Algebra k (m v)) =>
    [Vect k (m v)] -> GlexPoly Q String
hilbertPolyQA' gs = hilbertPolyQA vs gs where vs = toSet (concatMap vars gs)
dim vs gs = 1 + deg (hilbertPolyQA vs gs)
dim' gs = 1 + deg (hilbertPolyQA' gs)