{-# LANGUAGE CPP #-}

module Nettle.OpenFlow.Statistics (
  StatsRequest (..)
  , TableQuery (..)
#if OPENFLOW_VERSION==1    
  , PortQuery (..) 
  , QueueQuery (..)
#endif
  , StatsReply (..) 
  , MoreToFollowFlag
  , FlowStats (..) 
  , AggregateFlowStats (..) 
  , TableStats (..)
  , PortStats (..) 
  , nullPortStats
  , zeroPortStats
  , liftIntoPortStats1
  , liftIntoPortStats2
  , Description (..)
#if OPENFLOW_VERSION==1
  , QueueStats (..)
#endif
  ) where

import Data.Word
import Nettle.OpenFlow.Port
import Nettle.OpenFlow.Match
import Nettle.OpenFlow.Action
import Nettle.OpenFlow.FlowTable
import Control.Monad (liftM,liftM2)

data StatsRequest
    = FlowStatsRequest {
        statsRequestMatch   :: Match,           -- ^fields to match
        statsRequestTableID :: TableQuery,      -- ^ID of table to read
        statsRequestPort    :: Maybe PseudoPort -- ^if present, require matching entries to include this as an output port
      }
    | AggregateFlowStatsRequest { 
        statsRequestMatch   :: Match,           -- ^fields to match
        statsRequestTableID :: TableQuery,      -- ^ID of table to read
        statsRequestPort    :: Maybe PseudoPort -- ^if present, require matching entries to include this as an output port
      }
    | TableStatsRequest
    | DescriptionRequest
#if OPENFLOW_VERSION==151 || OPENFLOW_VERSION==152
    | PortStatsRequest
#endif
#if OPENFLOW_VERSION==1 
    | PortStatsRequest {
        portStatsQuery :: PortQuery
      }
    | QueueStatsRequest { queueStatsPort :: PortQuery, queueStatsQuery:: QueueQuery }
#endif
    deriving (Show,Eq)    

#if OPENFLOW_VERSION==1
data PortQuery = AllPorts | SinglePort PortID deriving (Show,Eq,Ord)
data QueueQuery = AllQueues | SingleQueue QueueID deriving (Show,Eq,Ord)
#endif

data TableQuery = AllTables 
#if OPENFLOW_VERSION==1
                | EmergencyTable
#endif
                | Table FlowTableID 
                  deriving (Show,Eq)



data StatsReply
    = DescriptionReply Description
    | FlowStatsReply MoreToFollowFlag [FlowStats]
    | AggregateFlowStatsReply AggregateFlowStats
    | TableStatsReply MoreToFollowFlag [TableStats]
    | PortStatsReply MoreToFollowFlag [(PortID,PortStats)]
#if OPENFLOW_VERSION==1
    | QueueStatsReply MoreToFollowFlag [QueueStats]
#endif
      deriving (Show,Eq)

type MoreToFollowFlag = Bool

data Description = Description { manufacturerDesc :: String
                                 , hardwareDesc     :: String
                                 , softwareDesc     :: String
                                 , serialNumber     :: String 
#if OPENFLOW_VERSION==1
                                 , datapathDesc     :: String 
#endif
                                 } deriving (Show,Eq)

data AggregateFlowStats = 
  AggregateFlowStats { aggregateFlowStatsPacketCount :: Integer, 
                       aggregateFlowStatsByteCount   :: Integer, 
                       aggregateFlowStatsFlowCount   :: Integer
                     } deriving (Show, Eq)
                       

data FlowStats = FlowStats {
      flowStatsTableID             :: FlowTableID, -- ^ Table ID of the flow
      flowStatsMatch               :: Match,       -- ^ Match condition of the flow
      flowStatsActions             :: [Action],    -- ^ Actions for the flow
      flowStatsPriority            :: Priority,    -- ^ Priority of the flow entry (meaningful when the match is not exact).
#if OPENFLOW_VERSION==1
      flowStatsCookie              :: Cookie,      -- ^ Cookie associated with the flow.
#endif
      flowStatsDurationSeconds     :: Integer,     
#if OPENFLOW_VERSION==1
      flowStatsDurationNanoseconds :: Integer,
#endif
      flowStatsIdleTimeout         :: Integer,
      flowStatsHardTimeout         :: Integer,
      flowStatsPacketCount         :: Integer,
      flowStatsByteCount           :: Integer
    }
    deriving (Show,Eq)

data TableStats = 
  TableStats { 
    tableStatsTableID      :: FlowTableID, 
    tableStatsTableName    :: String,
    tableStatsMaxEntries   :: Integer, 
    tableStatsActiveCount  :: Integer, 
    tableStatsLookupCount  :: Integer, 
    tableStatsMatchedCount :: Integer } deriving (Show,Eq)

data PortStats 
    = PortStats { 
        portStatsReceivedPackets      :: Maybe Double, 
        portStatsSentPackets          :: Maybe Double, 
        portStatsReceivedBytes        :: Maybe Double, 
        portStatsSentBytes            :: Maybe Double, 
        portStatsReceiverDropped      :: Maybe Double, 
        portStatsSenderDropped        :: Maybe Double, 
        portStatsReceiveErrors        :: Maybe Double, 
        portStatsTransmitError        :: Maybe Double, 
        portStatsReceivedFrameErrors  :: Maybe Double, 
        portStatsReceiverOverrunError :: Maybe Double, 
        portStatsReceiverCRCError     :: Maybe Double, 
        portStatsCollisions           :: Maybe Double
      } deriving (Show,Eq)

-- | A port stats value with all fields missing.
nullPortStats :: PortStats
nullPortStats = PortStats { 
  portStatsReceivedPackets      = Nothing,
  portStatsSentPackets          = Nothing,
  portStatsReceivedBytes        = Nothing,
  portStatsSentBytes            = Nothing,
  portStatsReceiverDropped      = Nothing,
  portStatsSenderDropped        = Nothing,
  portStatsReceiveErrors        = Nothing,
  portStatsTransmitError        = Nothing,
  portStatsReceivedFrameErrors  = Nothing,
  portStatsReceiverOverrunError = Nothing,
  portStatsReceiverCRCError     = Nothing,
  portStatsCollisions           = Nothing
  }

-- | A port stats value with all fields present, but set to 0.
zeroPortStats :: PortStats
zeroPortStats = 
    PortStats { 
      portStatsReceivedPackets      = Just 0, 
      portStatsSentPackets          = Just 0, 
      portStatsReceivedBytes        = Just 0, 
      portStatsSentBytes            = Just 0, 
      portStatsReceiverDropped      = Just 0, 
      portStatsSenderDropped        = Just 0, 
      portStatsReceiveErrors        = Just 0, 
      portStatsTransmitError        = Just 0, 
      portStatsReceivedFrameErrors  = Just 0, 
      portStatsReceiverOverrunError = Just 0, 
      portStatsReceiverCRCError     = Just 0, 
      portStatsCollisions           = Just 0
      }
    
-- | Lift a unary function and apply to every member of a PortStats record.    
liftIntoPortStats1 :: (Double -> Double) -> PortStats -> PortStats
liftIntoPortStats1 f pr1 = 
  PortStats { portStatsReceivedPackets      = liftM f (portStatsReceivedPackets pr1),
              portStatsSentPackets          = liftM f (portStatsSentPackets pr1),
              portStatsReceivedBytes        = liftM f (portStatsReceivedBytes pr1),
              portStatsSentBytes            = liftM f (portStatsSentBytes pr1),
              portStatsReceiverDropped      = liftM f (portStatsReceiverDropped pr1),
              portStatsSenderDropped        = liftM f (portStatsSenderDropped pr1),
              portStatsReceiveErrors        = liftM f (portStatsReceiveErrors pr1),
              portStatsTransmitError        = liftM f (portStatsTransmitError pr1),
              portStatsReceivedFrameErrors  = liftM f (portStatsReceivedFrameErrors pr1),
              portStatsReceiverOverrunError = liftM f (portStatsReceiverOverrunError pr1),
              portStatsReceiverCRCError     = liftM f (portStatsReceiverCRCError pr1),
              portStatsCollisions           = liftM f (portStatsCollisions pr1)
            }

-- | Lift a binary function and apply to every member of a PortStats record.    
liftIntoPortStats2 :: (Double -> Double -> Double) -> PortStats -> PortStats -> PortStats
liftIntoPortStats2 f pr1 pr2 = 
    PortStats { portStatsReceivedPackets      = liftM2 f (portStatsReceivedPackets pr1) (portStatsReceivedPackets pr2),
                portStatsSentPackets          = liftM2 f (portStatsSentPackets pr1) (portStatsSentPackets pr2),
                portStatsReceivedBytes        = liftM2 f (portStatsReceivedBytes pr1) (portStatsReceivedBytes pr2),
                portStatsSentBytes            = liftM2 f (portStatsSentBytes pr1) (portStatsSentBytes pr2),
                portStatsReceiverDropped      = liftM2 f (portStatsReceiverDropped pr1) (portStatsReceiverDropped pr2),
                portStatsSenderDropped        = liftM2 f (portStatsSenderDropped pr1) (portStatsSenderDropped pr2),
                portStatsReceiveErrors        = liftM2 f (portStatsReceiveErrors pr1) (portStatsReceiveErrors pr2),
                portStatsTransmitError        = liftM2 f (portStatsTransmitError pr1) (portStatsTransmitError pr2),
                portStatsReceivedFrameErrors  = liftM2 f (portStatsReceivedFrameErrors pr1) (portStatsReceivedFrameErrors pr2),
                portStatsReceiverOverrunError = liftM2 f (portStatsReceiverOverrunError pr1) (portStatsReceiverOverrunError pr2),
                portStatsReceiverCRCError     = liftM2 f (portStatsReceiverCRCError pr1) (portStatsReceiverCRCError pr2),
                portStatsCollisions           = liftM2 f (portStatsCollisions pr1) (portStatsCollisions pr2)
              }
    



#if OPENFLOW_VERSION==1
data QueueStats = QueueStats { queueStatsPortID             :: PortID, 
                               queueStatsQueueID            :: QueueID, 
                               queueStatsTransmittedBytes   :: Integer, 
                               queueStatsTransmittedPackets :: Integer, 
                               queueStatsTransmittedErrors  :: Integer } deriving (Show,Eq)
#endif