module Data.Ip
    ( Ip ()
    
    , readIp
    , showIp
    
    , fromOctets
    , toOctets
    
    , toWord32
    ) where
import Control.Monad (unless)
import Data.Bits
import Data.Text (Text)
import qualified Data.Text as T
import Data.Text.Read
import Data.Word
import Import
newtype Ip = Ip {unIp :: Word32}
   deriving (Eq, Ord, Bounded, Storable)
showIp :: Ip -> Text
showIp ip = T.intercalate "." . map (T.pack . show) $ [a, b, c, d]
  where (a, b, c, d) = toOctets ip
readIp :: Text -> Either String Ip
readIp s = fmapL (\e -> "Error parsing IP address: " ++ e) $ do
   (a, s2) <- decimal s
   (b, s3) <- dot s2 >>= decimal
   (c, s4) <- dot s3 >>= decimal
   (d, s5) <- dot s4 >>= decimal
   unless (s5 == "") $ Left "exactly 4 octets were expected."
   
   fromOctets <$> digit a <*> digit b <*> digit c <*> digit d
  where
    dot = note "Expected '.' character." . T.stripPrefix "."
    digit :: Int -> Either String Word8
    digit x | x < 0 || x > 255 = Left "digit out of range."
            | otherwise = Right $ fromIntegral x
fromOctets :: Word8 -> Word8 -> Word8 -> Word8 -> Ip
fromOctets a b c d = Ip
      $ (fromIntegral a `shiftL` 24)
    .|. (fromIntegral b `shiftL` 16)
    .|. (fromIntegral c `shiftL` 8)
    .|. (fromIntegral d)
toOctets :: Ip -> (Word8, Word8, Word8, Word8)
toOctets (Ip word) = (byte 3 word, byte 2 word, byte 1 word, byte 0 word)
  where
    byte i w = fromIntegral (w `shiftR` (i * 8))
toWord32 :: Ip -> Word32
toWord32 = unIp