{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE CPP, DisambiguateRecordFields, RecordWildCards #-}

module Nettle.OpenFlow.Packet (
  -- * Sending packets
  PacketOut (..)
  , bufferedPacketOut
  , unbufferedPacketOut
  , receivedPacketOut
  , BufferID
    
    -- * Packets not handled by a switch
  , PacketInfo (..)
  , PacketInReason (..) 
  , NumBytes
  , bufferedAtSwitch
  ) where

import qualified Data.ByteString as B
import Nettle.OpenFlow.Port
import Nettle.OpenFlow.Action
import Nettle.Ethernet.EthernetFrame
import Data.Word
import Data.Maybe (isJust)

-- | A switch can be remotely commanded to send a packet. The packet
-- can either be a packet buffered at the switch, in which case the
-- bufferID is provided, or it can be specified explicitly by giving 
-- the packet data.
data PacketOut 
    = PacketOutRecord {
        bufferIDData  :: !(Either BufferID B.ByteString),   -- ^either a buffer ID or the data itself
        packetInPort  :: !(Maybe PortID),                   -- ^the port at which the packet received, for the purposes of processing this command
        packetActions :: !ActionSequence                  -- ^actions to apply to the packet
      } deriving (Eq,Show)

-- |A switch may buffer a packet that it receives. 
-- When it does so, the packet is assigned a bufferID
-- which can be used to refer to that packet.
type BufferID = Word32

-- | Constructs a @PacketOut@ value for a packet buffered at a switch.
bufferedPacketOut :: BufferID -> Maybe PortID -> ActionSequence -> PacketOut
bufferedPacketOut bufID inPort actions = 
  PacketOutRecord { bufferIDData = Left bufID
                  , packetInPort       = inPort
                  , packetActions      = actions
                  } 

-- | Constructs a @PacketOut@ value for an unbuffered packet, including the packet data.
unbufferedPacketOut :: B.ByteString -> Maybe PortID -> ActionSequence -> PacketOut
unbufferedPacketOut pktData inPort actions  = 
  PacketOutRecord { bufferIDData = Right pktData
                  , packetInPort       = inPort
                  , packetActions      = actions
                  } 

-- | Constructs a @PacketOut@ value that processes the packet referred to by the @PacketInfo@ value 
-- according to the specified actions. 
receivedPacketOut :: PacketInfo -> ActionSequence -> PacketOut
receivedPacketOut (PacketInfo {..}) actions = 
    case bufferID of 
      Nothing    -> unbufferedPacketOut packetData (Just receivedOnPort) actions
      Just bufID -> bufferedPacketOut bufID (Just receivedOnPort) actions


-- | A switch receives packets on its ports. If the packet matches
-- some flow rules, the highest priority rule is executed. If no 
-- flow rule matches, the packet is sent to the controller. When 
-- packet is sent to the controller, the switch sends a message
-- containing the following information.
data PacketInfo 
    = PacketInfo {
        bufferID       :: !(Maybe BufferID), -- ^buffer ID if packet buffered
        packetLength   :: !NumBytes,       -- ^full length of frame
        receivedOnPort :: !PortID,         -- ^port on which frame was received
        reasonSent     :: !PacketInReason, -- ^reason packet is being sent
        packetData     :: !B.ByteString,   -- ^ethernet frame, includes full packet only if no buffer ID
        enclosedFrame  :: !(Either String EthernetFrame) -- ^result of parsing packetData field.
      } deriving (Show,Eq)


-- |A PacketInfo message includes the reason that the message
-- was sent, namely either there was no match, or there was
-- a match, and that match's actions included a Sent-To-Controller
-- action.
data PacketInReason = NotMatched | ExplicitSend deriving (Show,Read,Eq,Ord,Enum)

-- | The number of bytes in a packet.
type NumBytes = Int

bufferedAtSwitch :: PacketInfo -> Bool
bufferedAtSwitch = isJust . bufferID
