-- | Multivariate compact monomials where the variable set 
-- looks like @{x_1, x_2, ... , x_N}@. 
--
-- This is very similar to the \"Indexed\" version, but should have much more
-- compact in-memory representation (which is useful in case of large or many 
-- polynomials, and should be in theory also faster, because of cache friendlyness)
--

{-# LANGUAGE CPP, BangPatterns, TypeFamilies, DataKinds, KindSignatures, ScopedTypeVariables #-}
module Math.Algebra.Polynomial.Monomial.Compact where

--------------------------------------------------------------------------------

import Data.List
import Data.Word

import Data.Array.Unboxed  -- used only by compactFromList

#if MIN_VERSION_base(4,11,0)        
import Data.Semigroup
import Data.Monoid
#else
import Data.Monoid
#endif

import Data.Typeable
import GHC.TypeLits
import Data.Proxy

import Data.Foldable as F

import qualified Data.Vector.Compact.WordVec as V

import Math.Algebra.Polynomial.Class
import Math.Algebra.Polynomial.Pretty
import Math.Algebra.Polynomial.Misc

import Math.Algebra.Polynomial.Monomial.Indexed ( XS , xsFromExponents , xsToExponents )

--------------------------------------------------------------------------------
-- * Monomials

-- | Monomials of the variables @x1,x2,...,xn@. The internal representation is a
-- compact vector of the exponents.
--
-- The type is indexed by the /name/ of the variables, and then the /number/ of variables.
--
-- Note that we assume here that the internal vector has length @n@.
newtype Compact (var :: Symbol) (n :: Nat)
  = Compact V.WordVec
  deriving (Eq,Show,Typeable)

--------------------------------------------------------------------------------

-- note: this must be a monomial ordering!
instance Ord (Compact var n) where
  compare (Compact a) (Compact b) = compare a b

instance KnownNat n => Semigroup (Compact var n) where
  (<>) = mulCompact

instance KnownNat n => Monoid (Compact var n) where
  mempty  = emptyCompact
  mappend = mulCompact

instance KnownSymbol var => Pretty (Compact var n) where
  pretty monom =
    case [ showXPow i e | (i,e) <- zip [1..] es , e /= 0 ] of
      [] -> "(1)"
      xs -> intercalate "*" xs
    where
      es = compactToWordExpoList monom
      v  = compactVar monom
      showXPow !i !e = case e of
        0 -> "1"
        1 -> v ++ show i
        _ -> v ++ show i ++ "^" ++ show e

-- | Name of the variables
compactVar :: KnownSymbol var => Compact var n -> String
compactVar = symbolVal . varProxy where
  varProxy :: Compact var n -> Proxy var
  varProxy _ = Proxy

-- | Number of variables
nOfCompact :: KnownNat n => Compact var n -> Int
nOfCompact = fromInteger . natVal . natProxy where
  natProxy :: Compact var n -> Proxy n
  natProxy _ = Proxy

--------------------------------------------------------------------------------
-- * Conversion

-- | from @(variable,exponent)@ pairs
compactFromList :: KnownNat n => [(Index,Int)] -> Compact v n
compactFromList list = xs where
  xs  = Compact $ V.fromList {- n -} (elems arr)
  arr = accumArray (+) 0 (1,n) list' :: UArray Int Word
  n   = nOfCompact xs
  list' = map f list :: [(Int,Word)]
  f (Index j , e)
    | j < 1      = error "compactFromList: index out of bounds (too small)"
    | j > n      = error "compactFromList: index out of bounds (too big)"
    | e < 0      = error "compactFromList: negative exponent"
    | otherwise  = (j,fromIntegral e)

-- | to @(variable,exponent)@ pairs
compactToList :: Compact v n -> [(Index,Int)]
compactToList (Compact vec) = filter cond $ zipWith f [1..] (V.toList vec) where
  f j e = (Index j, fromIntegral e)
  cond (_,e) = e > 0

-- | from @Word@ exponent list
compactFromWordExpoList :: KnownNat n => [Word] -> Compact var n
compactFromWordExpoList ws = cpt where
  n   = nOfCompact cpt
  cpt = Compact vec
  vec = V.fromList {- n -} (take n (ws ++ repeat 0))

-- | to @Word@ exponent list
compactToWordExpoList :: Compact var n -> [Word]
compactToWordExpoList (Compact vec) = V.toList vec

-- | from @Int@ exponent list
compactFromExponents :: KnownNat n => [Int] -> Compact v n
compactFromExponents = compactFromWordExpoList . map fromIntegral

-- | to @Int@ exponent list
compactToExponents :: KnownNat n => Compact v n -> [Int]
compactToExponents = map fromIntegral . compactToWordExpoList

-- | from 'XS' exponent list
compactFromXS :: KnownNat n => XS v n -> Compact v n
compactFromXS = compactFromExponents . xsToExponents

-- | to 'XS' exponent list
compactToXS :: KnownNat n => Compact v n -> XS v n
compactToXS = xsFromExponents . compactToExponents

--------------------------------------------------------------------------------
-- * empty (all zero exponents)

emptyCompact :: KnownNat n => Compact v n
emptyCompact = xs where
  xs = Compact $ V.fromList' (V.Shape n 4) (replicate n (0::Word))
  n  = nOfCompact xs

isEmptyCompact :: Compact v n -> Bool
isEmptyCompact monom@(Compact vec) = (V.maximum vec == 0)
  -- all (==0) (compactToWordExpoList monom)

--------------------------------------------------------------------------------
-- * normalization

isNormalCompact :: KnownNat n => Compact v n -> Bool
isNormalCompact cpt@(Compact vec) = nOfCompact cpt == V.vecLen vec

--------------------------------------------------------------------------------
-- * creation

variableCompact :: KnownNat n => Index -> Compact v n
variableCompact idx = singletonCompact idx 1

singletonCompact :: KnownNat n => Index -> Int -> Compact v n
singletonCompact (Index j) e0
  | j < 1     = error "singletonCompact: index out of bounds (too small)"
  | j > n     = error "singletonCompact: index out of bounds (too big)"
  | e < 0     = error "singletonCompact: negative exponent"
  | otherwise = cpt
  where
    e    = fromIntegral e0 :: Word
    list = replicate (j-1) 0 ++ e : replicate (n-j) 0
    n    = nOfCompact cpt
    cpt  = Compact $ V.fromList' (V.Shape n (V.bitsNeededFor e)) list

--------------------------------------------------------------------------------
-- * products

mulCompact :: KnownNat n => Compact v n -> Compact v n -> Compact v n
mulCompact (Compact vec1) (Compact vec2) = Compact $ V.add vec1 vec2

productCompact :: (KnownNat n, Foldable f) => f (Compact v n) -> Compact v n
productCompact = F.foldl' mulCompact emptyCompact

powCompact :: KnownNat n => Compact v n -> Int -> Compact v n
powCompact (Compact vec) e
  | e < 0     = error "powCompact: negative exponent"
  | e == 0    = emptyCompact
  | otherwise = Compact $ V.scale (fromIntegral e) vec

divCompact :: KnownNat n => Compact v n -> Compact v n -> Maybe (Compact v n)
divCompact (Compact vec1) (Compact vec2) = Compact <$> V.subtract vec1 vec2

--------------------------------------------------------------------------------
-- * degree

maxDegCompact :: Compact v n -> Int
maxDegCompact (Compact vec) = fromIntegral (V.maximum vec)

totalDegCompact :: Compact v n -> Int
totalDegCompact (Compact vec) = fromIntegral (V.sum vec)

--------------------------------------------------------------------------------
-- * differentiation

diffCompact :: Num c => Index -> Int -> Compact v n -> Maybe (Compact v n, c)
diffCompact = error "diffCompact: not implemented yet"

{-
diffCompact :: Num c => Index -> Int -> Compact v n -> Maybe (Compact v n, c)
diffCompact _         0 cpt          = Just (cpt,1)
diffCompact (Index j) k (Compact ba) =
  if k8 > m8
    then Nothing
    else Just (Compact ba' , fromInteger c) 
  where
    k8   = fromIntegral k :: Word8
    m8   = indexByteArray ba (j-1) :: Word8
    m    = fromIntegral m8 :: Int
    ba'  = byteArrayFromList $ change $ byteArrayToList ba
    c    = product [ fromIntegral (m - i) | i<-[0..k-1] ] :: Integer
    change = go 1 where
      go i (x:xs) = if i == j then (x-k8) : xs else x : go (i+1) xs
      go i []     = [] 
-}

--------------------------------------------------------------------------------

instance (KnownNat n, KnownSymbol v) => Monomial (Compact v n) where
  type VarM (Compact v n) = Index
  normalizeM = id
  isNormalM  = isNormalCompact
  fromListM  = compactFromList
  toListM    = compactToList
  emptyM     = emptyCompact
  isEmptyM   = isEmptyCompact
  variableM  = variableCompact
  singletonM = singletonCompact
  mulM       = mulCompact
  divM       = divCompact
  productM   = productCompact
  powM       = powCompact
  maxDegM    = maxDegCompact
  totalDegM  = totalDegCompact
  diffM      = diffCompact

  evalM      = error "Compact/evalM: not yet implemented"
  varSubsM   = error "Compact/varSubsM: not yet implemented"
  termSubsM  = error "Compact/termSubsM: not yet implemented"

--------------------------------------------------------------------------------