{-# LANGUAGE TemplateHaskell, TypeOperators #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeSynonymInstances #-}

module Network.Protocol.Snmp.AgentX.Packet.Types 
( PDU(..)
, Version
, SessionID
, TransactionID
, PacketID
, econvert
, Context(..)
, Tag(..)
, TaggedError(..)
, TestError(..)
, CommitError(..)
, RError(..)
, UndoError(..)
, SysUptime
, Index
, MaxRepeaters
, NonRepeaters
-- ** Packet
, Packet
-- *** constructor
, mkPacket
-- *** lenses
, flags
, version 
, pdu
, sid
, pid
, tid
-- ** SearchRange 
, SearchRange
-- *** constructor
, mkSearchRange
-- *** lenses
, startOID
, endOID
, include
-- ** VarBind containt Value and OID
, VarBind
-- *** constructor
, mkVarBind
-- *** lenses
, vboid
, vbvalue
-- ** Flags 
, Flags
-- *** constructor
, mkFlags
, InstanceRegistration 
, NewIndex 
, AnyIndex 
, NonDefaultContext 
, BigEndian 
-- *** lenses
, instanceRegistration
, newIndex
, anyIndex
, nonDefaultContext
, bigEndian
)
where

import Data.Label

import Data.Word
import Data.ByteString (ByteString)
import Data.String
import Data.Default

import Network.Protocol.Snmp (Value(..), OID)

-- | protocol version (const 1 by default)
newtype Version = Version Word8 deriving (Show, Eq, Enum, Bounded, Ord)

-- | session id in header, rfc 2741, section 6.1
newtype SessionID = SessionID Word32 deriving (Show, Eq, Enum, Bounded, Ord)

-- | transaction id in header, rfc 2741, section 6.1
newtype TransactionID = TransactionID Word32 deriving (Show, Eq, Ord, Enum, Bounded)

-- | packet id in header, rfc 2741, section 6.1
newtype PacketID = PacketID Word32 deriving (Show, Eq, Ord, Enum, Bounded)

-- | helper for convert 
econvert :: (Enum a, Enum b) => a -> b
econvert = toEnum . fromEnum

-- | rfc 2571 section 3.3.1, rfc 2741 section 6.1.1 Context
newtype Context = Context ByteString deriving (Show, Ord, Eq, IsString)

-- | rfc 2741, section 6.2.2, Error status in agentx-close-pdu
data Reason = Other
            | ParseError
            | ProtocolError
            | Timeouts
            | Shutdown
            | ByManager
            deriving (Show, Eq)

-- | rfc 2741, 6.2.16, Error status in agentx-response-pdu
data RError = NoAgentXError
            | OpenFailed
            | NotOpen
            | IndexWrongType
            | IndexAlreadyAllocated
            | IndexNonAvailable
            | IndexNotAllocated
            | UnsupportedContext
            | DuplicateRegistration
            | UnknownRegistration
            | UnknownAgentCaps
            | RParseError
            | RequestDenied
            | ProcessingError
            deriving (Show, Eq)

-- | result for testSetAIO (rfc 2741, section 7.2.4.1)
data TestError 
            = NoTestError
            | TooBig
            | NoSuchName
            | BadValue
            | ReadOnly
            | GenError
            | NoAccess
            | WrongType
            | WrongLength
            | WrongEncoding
            | WrongValue
            | NoCreation
            | InconsistentValue
            | ResourceUnavailable
            | NotWritable
            | InconsistentName
            deriving (Show, Eq)

-- | result for commitSetAIO (rfc 2741, section 7.2.4.2)
data CommitError
            = NoCommitError
            | CommitFailed
            deriving (Show, Eq) 
                  
-- | result for undoSetAIO (rfc 2741, section 7.2.4.3)
data UndoError
            = NoUndoError
            | UndoFailed
            deriving (Show, Eq)
          
type Timeout = Word8
type Priority = Word8
type RangeSubid = Word8
type Description = ByteString
type MContext = Maybe Context
type UpperBound = Maybe Word32
type SysUptime = Word32
type Index = Word16
type NonRepeaters = Word16
type MaxRepeaters = Word16

-- | Error with Tag instance
data TaggedError = forall a. (Show a, Eq a, Tag a Word16) => Tagged a 

instance Tag TaggedError Word16 where
    tag (Tagged x) = tag x
    unTag x 
      | x == 0 = Tagged NoAgentXError
      | x > 0 && x <= 18 = Tagged (unTag x :: TestError)
      | otherwise = Tagged (unTag x :: RError)

instance Eq TaggedError where
    x == y = (tag x :: Word16) == (tag y :: Word16)

instance Show TaggedError where
    show (Tagged a) = show a

    
-- | rfc 2741, section 6.2
data PDU = Open Timeout OID Description -- ^ section 6.2.1
         | Close  Reason                 -- ^ section 6.2.2
         | Register MContext Timeout Priority RangeSubid OID UpperBound -- ^ section 6.2.3
         | Unregister MContext Priority RangeSubid OID UpperBound -- ^ section 6.2.4
         | Get MContext [OID] -- ^ section 6.2.5
         | GetNext MContext [SearchRange] -- ^ section 6.2.6
         | GetBulk MContext NonRepeaters MaxRepeaters [SearchRange] -- ^ section 6.2.7
         | TestSet MContext [VarBind] -- ^ section 6.2.8
         | CommitSet -- ^ section 6.2.9
         | UndoSet -- ^ section 6.2.9
         | CleanupSet -- ^ section 6.2.9
         | Notify MContext [VarBind] -- ^ section 6.2.10
         | Ping MContext -- ^ section 6.2.11
         | IndexAllocate MContext [VarBind] -- ^ section 6.2.12
         | IndexDeallocate MContext [VarBind] -- ^ section 6.2.13
         | AddAgentCaps MContext OID Description -- ^ section 6.2.14
         | RemoveAgentCaps MContext OID  -- ^ section 6.2.15
         | Response SysUptime TaggedError Index [VarBind] -- ^ section 6.2.16
         deriving (Show, Eq)


-- | class for convert Errors to Word* and Word* to Errors
class Tag a b where
    tag :: a -> b
    unTag :: b -> a

-- | Packet type, describe agentx packet.
data Packet = Packet 
  { _version :: Version 
  , _pdu     :: PDU 
  , _flags   :: Flags 
  , _sid     :: SessionID 
  , _tid     :: TransactionID 
  , _pid     :: PacketID 
  } deriving Show

mkPacket :: Version -> PDU -> Flags -> SessionID -> TransactionID -> PacketID -> Packet
mkPacket = Packet

-- | header flags, rfc 2741, section 6.1
data Flags = Flags 
  { _instanceRegistration :: InstanceRegistration 
  , _newIndex             :: NewIndex
  , _anyIndex             :: AnyIndex
  , _nonDefaultContext    :: NonDefaultContext
  , _bigEndian            :: BigEndian
  } deriving (Show)

type InstanceRegistration = Bool
type NewIndex = Bool
type AnyIndex = Bool
type NonDefaultContext = Bool
type BigEndian = Bool

mkFlags :: InstanceRegistration -> NewIndex -> AnyIndex -> NonDefaultContext -> BigEndian -> Flags
mkFlags = Flags

-- | used for getnext and other requests (rfc 2741, section5.2 )
data SearchRange = SearchRange 
  { _startOID :: OID 
  , _endOID   :: OID
  , _include  :: Bool
  } deriving (Show, Eq)

-- | create SearchRange
mkSearchRange :: OID -> OID -> Bool -> SearchRange
mkSearchRange = SearchRange

-- | containt oid and value (rfc 2741, section 5.4)
data VarBind = VarBind 
  { _vboid   :: OID 
  , _vbvalue :: Value 
  } deriving (Show, Eq)

-- | constructor for VarBind
mkVarBind :: OID -> Value -> VarBind
mkVarBind = VarBind 

mkLabels [''Packet, ''Flags, ''VarBind, ''SearchRange ]

instance Tag Reason Word8 where
    tag Other         = 1
    tag ParseError    = 2
    tag ProtocolError = 3
    tag Timeouts      = 4
    tag Shutdown      = 5
    tag ByManager     = 6
    unTag 1 = Other        
    unTag 2 = ParseError   
    unTag 3 = ProtocolError
    unTag 4 = Timeouts     
    unTag 5 = Shutdown     
    unTag 6 = ByManager    
    unTag _ = error "unknown reasonFromTag"

instance Tag RError Word16 where
    tag NoAgentXError         = 0
    tag OpenFailed            = 256
    tag NotOpen               = 257
    tag IndexWrongType        = 258
    tag IndexAlreadyAllocated = 259
    tag IndexNonAvailable     = 260
    tag IndexNotAllocated     = 261
    tag UnsupportedContext    = 262
    tag DuplicateRegistration = 263
    tag UnknownRegistration   = 264
    tag UnknownAgentCaps      = 265
    tag RParseError           = 266
    tag RequestDenied         = 267
    tag ProcessingError       = 268
    unTag 0   = NoAgentXError        
    unTag 256 = OpenFailed           
    unTag 257 = NotOpen              
    unTag 258 = IndexWrongType       
    unTag 259 = IndexAlreadyAllocated
    unTag 260 = IndexNonAvailable    
    unTag 261 = IndexNotAllocated    
    unTag 262 = UnsupportedContext   
    unTag 263 = DuplicateRegistration
    unTag 264 = UnknownRegistration  
    unTag 265 = UnknownAgentCaps     
    unTag 266 = RParseError          
    unTag 267 = RequestDenied        
    unTag 268 = ProcessingError      
    unTag _   = error "bad rerror"        

instance Tag TestError Word16 where
    tag NoTestError           = 0
    tag TooBig                = 1
    tag NoSuchName            = 2
    tag BadValue              = 3
    tag ReadOnly              = 4
    tag GenError              = 5
    tag NoAccess              = 6
    tag WrongType             = 7
    tag WrongLength           = 8
    tag WrongEncoding         = 9
    tag WrongValue            = 10
    tag NoCreation            = 11
    tag InconsistentValue     = 12
    tag ResourceUnavailable   = 13
    tag NotWritable           = 17
    tag InconsistentName      = 18
    unTag 0   = NoTestError
    unTag 1   = TooBig                
    unTag 2   = NoSuchName            
    unTag 3   = BadValue              
    unTag 4   = ReadOnly              
    unTag 5   = GenError            
    unTag 6   = NoAccess            
    unTag 7   = WrongType           
    unTag 8   = WrongLength         
    unTag 9   = WrongEncoding       
    unTag 10  = WrongValue          
    unTag 11  = NoCreation          
    unTag 12  = InconsistentValue   
    unTag 13  = ResourceUnavailable 
    unTag 17  = NotWritable         
    unTag 18  = InconsistentName    
    unTag _   = error "unknown tag in TestError"

instance Tag CommitError Word16 where
    tag NoCommitError = 0 
    tag CommitFailed  = 14
    unTag 0 = NoCommitError
    unTag 14 = CommitFailed 
    unTag _ = error "unknown tag in CommitError"

instance Tag UndoError Word16 where
    tag NoUndoError           = 0
    tag UndoFailed            = 15
    unTag 0   = NoUndoError
    unTag 15  = UndoFailed    
    unTag _   = error "unknown tag in UndoError"



instance Tag PDU Word8 where
    tag Open{}            = 1
    tag Close{}           = 2
    tag Register{}        = 3
    tag Unregister{}      = 4
    tag Get{}             = 5
    tag GetNext{}         = 6
    tag GetBulk{}         = 7
    tag TestSet{}         = 8
    tag CommitSet{}       = 9
    tag UndoSet{}         = 10
    tag CleanupSet{}      = 11
    tag Notify{}          = 12
    tag Ping{}            = 13
    tag IndexAllocate{}   = 14
    tag IndexDeallocate{} = 15
    tag AddAgentCaps{}    = 16
    tag RemoveAgentCaps{} = 17
    tag Response{}        = 18
    unTag _ = undefined

instance Tag Value Word16 where
-- TODO check zerodotzero 
    tag (ZeroDotZero)       = 0
    tag (Integer _)         = 2
    tag (String _)          = 4
    tag (Zero)              = 5
    tag (OI _)              = 6
    tag (IpAddress _ _ _ _) = 64
    tag (Counter32 _)       = 65
    tag (Gauge32 _)         = 66
    tag (TimeTicks _)       = 67
    tag (Opaque _)          = 68
    tag (Counter64 _)       = 70
    tag (NoSuchObject)      = 128
    tag (NoSuchInstance)    = 129
    tag (EndOfMibView)      = 130
    unTag _ = undefined

instance Default Flags where
    def = mkFlags False False False False False

instance Default Version where
    def = Version 1