{-# LANGUAGE TemplateHaskell #-}

module Network.Data.OpenFlow.Statistics (
  StatsRequest (..)
  , TableQuery (..)
  , PortQuery (..) 
  , QueueQuery (..)
  , StatsReply (..) 
  , MoreToFollowFlag
  , FlowStats (..) 
  , AggregateFlowStats (..) 
  , TableStats (..)
  , PortStats (..) 
  , nullPortStats
  , zeroPortStats
  , liftIntoPortStats1
  , liftIntoPortStats2
  , Description (..)
  , QueueStats (..)
  ) where

import Network.Data.OpenFlow.Port
import Network.Data.OpenFlow.Match
import Network.Data.OpenFlow.Action
import Network.Data.OpenFlow.FlowTable
import Control.Monad (liftM,liftM2)
import Data.Aeson.TH
import Data.Int

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
    | PortStatsRequest {
        portStatsQuery :: PortQuery
      }
    | QueueStatsRequest { queueStatsPort :: PortQuery, queueStatsQuery:: QueueQuery }
    deriving (Show,Eq)    

data PortQuery = AllPorts | SinglePort PortID deriving (Show,Eq,Ord)
data QueueQuery = AllQueues | SingleQueue QueueID deriving (Show,Eq,Ord)

data TableQuery = AllTables 
                | EmergencyTable
                | Table FlowTableID 
                  deriving (Show,Eq)



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

type MoreToFollowFlag = Bool

data Description = Description { manufacturerDesc :: String
                                 , hardwareDesc     :: String
                                 , softwareDesc     :: String
                                 , serialNumber     :: String 
                                 , datapathDesc     :: String 
                                 } 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).
      flowStatsCookie              :: !Cookie,      -- ^ Cookie associated with the flow.
      flowStatsDurationSeconds     :: !Int,     
      flowStatsDurationNanoseconds :: !Int,
      flowStatsIdleTimeout         :: !Int,
      flowStatsHardTimeout         :: !Int,
      flowStatsPacketCount         :: !Int64,
      flowStatsByteCount           :: !Int64
    }
    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)
              }
    




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

$(deriveJSON defaultOptions ''PortStats)

