module Ceilometer.Fold
  ( 
    Known(..)
  , FoldResult(..)
    
  , foldCPU
  , foldDiskRead
  , foldDiskWrite
  , foldNeutronRx
  , foldNeutronTx
  , foldVolume
  , foldSSD
  , foldImage
  , foldImagePollster
  , foldInstanceFlavor
  , foldInstanceVCPU
  , foldInstanceRAM
  , foldInstanceDisk
  , foldIP
  , foldSnapshot
    
  , timewrapFold
  ) where
import qualified Control.Foldl    as L
import           Control.Lens     hiding (Fold, Simple)
import           Data.Map.Strict  (Map)
import qualified Data.Map.Strict  as M
import           Data.Word
import           Ceilometer.Types
import           Vaultaire.Types
data FoldResult
  = RSingle   !Word64
  | RMapNum32 !(Map PFValue32 Word64)
  | RMapNum64 !(Map PFValue64 Word64)
  | RMapText  !(Map PFValueText Word64)
class Known a where
  mkPrism :: Env -> APrism' Word64 a
  mkFold  :: Env -> L.Fold (Timed a) FoldResult
instance Known PDCPU where
  mkPrism _ = prSimple . pdCPU
  mkFold  _ = fmap RSingle (timewrapFold foldCPU)
instance Known PDDiskRead where
  mkPrism _ = prSimple . pdDiskRead
  mkFold  _ = fmap RSingle (timewrapFold foldDiskRead)
instance Known PDDiskWrite where
  mkPrism _ = prSimple . pdDiskWrite
  mkFold  _ = fmap RSingle (timewrapFold foldDiskWrite)
instance Known PDNeutronTx where
  mkPrism _ = prSimple . pdNeutronTx
  mkFold  _ = fmap RSingle (timewrapFold foldNeutronTx)
instance Known PDNeutronRx where
  mkPrism _ = prSimple . pdNeutronRx
  mkFold  _ = fmap RSingle (timewrapFold foldNeutronRx)
instance Known PDImagePollster where
  mkPrism _ = prSimple . pdImagePollster
  mkFold  _ = fmap RMapNum64 foldImagePollster
instance Known PDVolume where
  mkPrism _ = prCompoundEvent . pdVolume
  mkFold (Env _ _ _ (TimeStamp s) (TimeStamp e))
    = fmap RSingle (foldVolume (s,e))
instance Known PDSSD where
  mkPrism _  = prCompoundEvent . pdSSD
  mkFold (Env _ _ _ (TimeStamp s) (TimeStamp e))
    = fmap RSingle (foldSSD (s,e))
instance Known PDImage where
  mkPrism _  = prCompoundEvent . pdImage
  mkFold (Env _ _ _ (TimeStamp s) (TimeStamp e))
    = fmap RSingle (foldImage (s,e))
instance Known PDSnapshot where
  mkPrism _  = prCompoundEvent . pdSnapshot
  mkFold (Env _ _ _ (TimeStamp s) (TimeStamp e))
    = fmap RSingle (foldSnapshot (s,e))
instance Known PDIP where
  mkPrism _  = prCompoundEvent . pdIP
  mkFold (Env _ _ _ (TimeStamp s) (TimeStamp e))
    = fmap RSingle (foldIP (s,e))
instance Known PDInstanceVCPU where
  mkPrism _                = prCompoundPollster . pdInstanceVCPU
  mkFold  (Env _ _  f _ _) = fmap RMapNum32
    $ foldInstanceVCPU
    $ filterByInstanceStatus f (\(PDInstanceVCPU s _) -> s)
instance Known PDInstanceRAM where
  mkPrism _                = prCompoundPollster . pdInstanceRAM
  mkFold  (Env _ _  f _ _) = fmap RMapNum32
    $ foldInstanceRAM 
    $ filterByInstanceStatus f (\(PDInstanceRAM s _) -> s)
instance Known PDInstanceDisk where
  mkPrism _ = prCompoundPollster . pdInstanceDisk
  mkFold  (Env _ _  f _ _) = fmap RMapNum32
    $ foldInstanceDisk 
    $ filterByInstanceStatus f (\(PDInstanceDisk s _) -> s)
instance Known PDInstanceFlavor where
  mkPrism (Env fm _ _ _ _) = prCompoundPollster . pdInstanceFlavor fm
  mkFold  (Env _ _  f _ _) = fmap RMapText
    $ foldInstanceFlavor 
    $ filterByInstanceStatus f (\(PDInstanceFlavor s _) -> s)
foldCPU :: L.Fold PDCPU Word64
foldCPU = L.Fold sCumulative bCumulative eCumulative
foldDiskRead :: L.Fold PDDiskRead Word64
foldDiskRead = L.Fold sCumulative bCumulative eCumulative
foldDiskWrite :: L.Fold PDDiskWrite Word64
foldDiskWrite = L.Fold sCumulative bCumulative eCumulative
foldNeutronTx :: L.Fold PDNeutronTx Word64
foldNeutronTx = L.Fold sCumulative bCumulative eCumulative
foldNeutronRx :: L.Fold PDNeutronRx Word64
foldNeutronRx = L.Fold sCumulative bCumulative eCumulative
foldVolume :: Window -> L.Fold (Timed PDVolume) Word64
foldVolume window = L.Fold step bEvent (eEvent window standardEventFolder)
  where step (prev, acc) (Timed end (PDVolume _ VolumeDelete _ _)) = (Nothing, go end acc prev)
        step a            x                                        = sEvent window  a x
        
        go end acc (Just x) = insertVal x (end  x ^. time) acc
        go _   acc Nothing  = acc
foldSSD :: Window -> L.Fold (Timed PDSSD) Word64
foldSSD window = L.Fold step bEvent (eEvent window standardEventFolder)
  where step (prev, acc) (Timed end (PDSSD _ VolumeDelete _ _)) = (Nothing, go end acc prev)
        step a            x                                     = sEvent window  a x
        
        go end acc (Just x) = insertVal x (end  x ^. time) acc
        go _   acc Nothing  = acc
foldImage :: Window -> L.Fold (Timed PDImage) Word64
foldImage window = L.Fold step bEvent (eEvent window standardEventFolder)
  where step (prev, acc) (Timed end (PDImage _ ImageDelete _ _)) = (Nothing, go end acc prev)
        step a            x                                      = sEvent window  a x
        
        go end acc (Just x) = insertVal x (end  x ^. time) acc
        go _   acc Nothing  = acc
foldSnapshot :: Window -> L.Fold (Timed PDSnapshot) Word64
foldSnapshot window = L.Fold step bEvent (eEvent window standardEventFolder)
  where step (prev, acc) (Timed end (PDSnapshot _ SnapshotDelete _ _)) = (Nothing, go end acc prev)
        step a            x                                            = sEvent window  a x
        
        go end acc (Just x) = insertVal x (end  x ^. time) acc
        go _   acc Nothing  = acc
foldIP :: Window ->  L.Fold (Timed PDIP) Word64
foldIP window = L.Fold step bEvent (eEvent window ipEventFolder)
  where step (prev, acc) (Timed end (PDIP _ IPDelete _ _)) = (Nothing, go end acc prev)
        step a            x                                = sEvent window  a x
        
        go end acc (Just x) = insertVal x (end  x ^. time) acc
        go _   acc Nothing  = acc
foldInstanceFlavor   :: (PDInstanceFlavor -> Bool)
                     -> L.Fold (Timed PDInstanceFlavor) (Map PFValueText Word64)
foldInstanceFlavor f = L.Fold (sGaugePollster f) bGaugePollster snd
foldInstanceVCPU     :: (PDInstanceVCPU -> Bool)
                     -> L.Fold (Timed PDInstanceVCPU) (Map PFValue32 Word64)
foldInstanceVCPU   f =  L.Fold (sGaugePollster f) bGaugePollster snd
foldInstanceRAM      :: (PDInstanceRAM -> Bool)
                     -> L.Fold (Timed PDInstanceRAM)    (Map PFValue32 Word64)
foldInstanceRAM    f =  L.Fold (sGaugePollster f) bGaugePollster snd
foldInstanceDisk     :: (PDInstanceDisk -> Bool)
                     -> L.Fold (Timed PDInstanceDisk)   (Map PFValue32 Word64)
foldInstanceDisk   f =  L.Fold (sGaugePollster f) bGaugePollster snd
foldImagePollster  :: L.Fold (Timed PDImagePollster)  (Map PFValue64 Word64)
foldImagePollster  =  L.Fold (sGaugePollster $ const True) bGaugePollster snd
timewrapFold :: L.Fold x y -> L.Fold (Timed x) y
timewrapFold (L.Fold s b e) = L.Fold (\a (Timed _ x) -> s a x) b e
type Window   = (Word64, Word64)
insertVal   x = M.insertWith (+) (x ^. value)
type ACumulative x = ( Maybe ( PFValue x  
                             , PFValue x) 
                     , Word64 )           
sCumulative :: (Valued x, Ord (PFValue x), Integral (PFValue x))
            => ACumulative x -> x -> ACumulative x
sCumulative (Nothing,          acc) x | v <- x ^. value = (Just (v, v), acc)
sCumulative (Just (f, latest), acc) x | v <- x ^. value =
  if    v < latest
  then (Just (f, v), acc + fromIntegral latest)
  else (Just (f, v), acc)
bCumulative = (Nothing, 0)
eCumulative (Just (first, latest), acc) = acc + latest  first
eCumulative (_, acc)                    = acc
type AGaugePollster x = ( Maybe (Timed x)          
                        , Map (PFValue x) Word64 ) 
sGaugePollster
  :: (Valued x, Ord (PFValue x))
  => (x -> Bool) -> AGaugePollster x -> Timed x -> AGaugePollster x
sGaugePollster _          (Nothing,            acc) x =  (Just x, acc)
sGaugePollster isBillable (Just (Timed t1 v1), acc) x@(Timed t2 _)
  = let delta = t2  t1
        !acc' = if   isBillable v1
                then insertVal v1 (fromIntegral delta) acc
                else acc
    in (Just x, acc')
bGaugePollster = (Nothing, M.empty)
type AEvent x = ( Maybe (Timed x)          
                , Map (PFValue x) Word64 ) 
bEvent = (Nothing, M.empty)
sEvent :: (Valued x, Ord (PFValue x), Show (PFValue x))
        => Window -> AEvent x -> Timed x -> AEvent x
sEvent _ (Nothing, acc) x = (Just x, acc)
sEvent (start, end) (Just prev, acc) x
  = if | prev ^. time > end   -> (Nothing, acc) 
       | prev ^. time < start -> (Just x, acc)  
       | d <= 0               -> (Nothing, acc) 
       | otherwise            -> (Just x, insertVal prev d acc)
  where s = max start (prev ^. time)
        e = min end   (x    ^. time)
        d = e  s
eEvent (start, end) f = M.foldlWithKey f 0 . go
  where s x = max start (x ^. time)
        
        go (Just x, acc)
          | d <- end  s x, d > 0 = insertVal x d acc
        go a@_                    = snd a
standardEventFolder a k v = a + (fromIntegral k * v)
ipEventFolder       a _ v = a + v