{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE DeriveGeneric #-}

module Network.Data.Ethernet.EthernetAddress ( 
  -- * Ethernet address
  EthernetAddress
  , ethernetAddress
  , ethernetAddress64
  , unpack
  , unpack64
  , toList
  , pack_32_16
  , isReserved
  , broadcastAddress
  , prettyPrintEthernetAddress
    
    -- * Parsers and unparsers
  , getEthernetAddress
  , putEthernetAddress
  ) where

import Data.Aeson
import Data.Word
import Data.Bits
import qualified Data.Binary.Get as Strict
import qualified Data.Binary.Put as Strict
import Numeric (showHex, readHex)
import Network.Data.Util
import GHC.Generics (Generic)
import Data.Hashable
import Data.Typeable
import Control.DeepSeq.Generics

-- | An Ethernet address consists of 6 bytes. It is stored in a single 64-bit value.
newtype EthernetAddress = EthernetAddress Word64 
                        deriving ( Eq
                                 , Ord
                                 , Generic
                                 , Typeable
                                 , Bits
                                 , Integral
                                 , Real
                                 , Enum
                                 , Num
                                 )

instance Hashable EthernetAddress
instance ToJSON EthernetAddress                                 
instance FromJSON EthernetAddress
instance NFData EthernetAddress where rnf = genericRnf

{-
prettyPrintEthernetAddress :: EthernetAddress -> String
prettyPrintEthernetAddress eth
  = concat $ intersperse ":" (map (\n -> showHex n "") 
                              [w0,w1,w2,w3,w4,w5])
  where (w0,w1,w2,w3,w4,w5) = unpack eth
-}
-- | Builds an ethernet address from a Word64 value. 
-- The two most significant bytes are irrelevant; only the bottom 6 bytes are used.
ethernetAddress64 :: Word64 -> EthernetAddress
ethernetAddress64 w64 = EthernetAddress (w64 `mod` 0x01000000000000)
{-# INLINE ethernetAddress64 #-}

ethernetAddress :: Word8 -> Word8 -> Word8 -> Word8 -> Word8 -> Word8 -> EthernetAddress                                
ethernetAddress w1 w2 w3 w4 w5 w6  
  = let w64 = (shiftL (fromIntegral w1) 40) .|.
              (shiftL (fromIntegral w2) 32) .|.
              (shiftL (fromIntegral w3) 24) .|.                       
              (shiftL (fromIntegral w4) 16) .|.                                              
              (shiftL (fromIntegral w5)  8) .|.                                              
              (fromIntegral w6)
    in EthernetAddress w64
                                
pack_32_16 :: Word32 -> Word16 -> Word64
pack_32_16 w32 w16 
  = (fromIntegral w32 `shiftL` 16) .|. fromIntegral w16
{-# INLINE pack_32_16 #-}

--  (W32# w32) (W16# w16) 
--  = W64# ((w32 `uncheckedShiftL#` 16#) `or#` w16)

unpack :: EthernetAddress -> (Word8,Word8,Word8,Word8,Word8,Word8)
unpack (EthernetAddress w64) = 
  let !a1 = fromIntegral (shiftR w64 40)
      !a2 = fromIntegral (shiftR w64 32 `mod` 0x0100)
      !a3 = fromIntegral (shiftR w64 24 `mod` 0x0100)
      !a4 = fromIntegral (shiftR w64 16 `mod` 0x0100)
      !a5 = fromIntegral (shiftR w64 8 `mod` 0x0100)
      !a6 = fromIntegral (w64 `mod` 0x0100)
  in (a1,a2,a3,a4,a5,a6)
{-# INLINE unpack #-}

unpack64 :: EthernetAddress -> Word64
unpack64 (EthernetAddress e) = e
{-# INLINE unpack64 #-}

-- | Parse an Ethernet address from a ByteString
getEthernetAddress :: Strict.Get EthernetAddress                                
getEthernetAddress = 
  do w32 <- Strict.getWord32be  
     w16 <- Strict.getWord16be
     return (EthernetAddress (pack_32_16 w32 w16))
{-# INLINE getEthernetAddress #-}     

-- | Unparse an Ethernet address to a ByteString     
putEthernetAddress :: EthernetAddress -> Strict.Put     
putEthernetAddress (EthernetAddress w64) 
  = do Strict.putWord32be (fromIntegral (shiftR w64 16))
       Strict.putWord16be (fromIntegral (w64 `mod` 0x010000))
{-# INLINE putEthernetAddress #-}    

isReserved :: EthernetAddress -> Bool
isReserved e = 
  let (a1, a2, a3, a4, a5, _) = unpack e
  in 
    a1 == 0x01 && 
    a2 == 0x80 && 
    a3 == 0xc2 && 
    a4 == 0 && 
    ((a5 .&. 0xf0) == 0)

broadcastAddress :: EthernetAddress
broadcastAddress = EthernetAddress 0xffffffffffff


-- Show & Read instances
--
-- Specifications: 
-- (A) unConcatIntersperse c . concatIntersperse [c] == id
-- (B) parseHex1 . unparseHex1 == id
-- (C) fromList . toList == id
--
-- Then, we reason: 
--
--   hexParseEthernetAddress . prettyPrintEthernetAddress
--
--    { expanding definitions }
--
-- == fromList . map parseHex1
--    . unConcatIntersperse ':' . concatIntersperse ":"
--    . map (\n -> showHex n "") . toList
--
--    { By prop (A) }
--
-- == fromList . map parseHex1 . map unparseHex1 . toList
--
--    { map-map fusion }
--
-- == fromList . map (parseHex1 . unparseHex1) . toList
--
--    { By prop (B) }
--
-- == fromList . map id . toList
--
--    { map functor law }
--
-- == fromList . toList
--
--    { By prop (C) }
--
-- == id

prettyPrintEthernetAddress :: EthernetAddress -> String
prettyPrintEthernetAddress =
  concatIntersperse ":" . map unparseHex1 . toList

hexParseEthernetAddress :: String -> EthernetAddress
hexParseEthernetAddress = fromList . hexParseWords 

hexParseWords :: String -> [Word8]
hexParseWords = map parseHex1 . unConcatIntersperse ':' 

parseHex1 :: String -> Word8
parseHex1 = fst . head . readHex

unparseHex1 :: Word8 -> String
unparseHex1 n
  | n < 16    = '0' : showHex n ""
  | otherwise = showHex n ""             

toList :: EthernetAddress -> [Word8]
toList eth = [w0,w1,w2,w3,w4,w5]
  where (w0,w1,w2,w3,w4,w5) = unpack eth

fromList :: [Word8] -> EthernetAddress
fromList [w0,w1,w2,w3,w4,w5] = ethernetAddress w0 w1 w2 w3 w4 w5
fromList _ = error "Incorrect number of bytes to construct EthernetAddress."

instance Show EthernetAddress where
  show = prettyPrintEthernetAddress

instance Read EthernetAddress where
  readsPrec _ s = [(hexParseEthernetAddress s,"")]

