module Arbor.File.Format.Asif.Get where

import Arbor.File.Format.Asif.ByteString.Builder
import Arbor.File.Format.Asif.Data.Ip            (word32ToIpv4, word32x4ToIpv6)
import Control.Lens
import Control.Monad
import Data.Binary.Get
import Data.Monoid                               ((<>))
import Data.Text                                 (Text)
import Data.Thyme.Clock                          (microseconds)
import Data.Thyme.Clock.POSIX                    (POSIXTime)
import Data.Thyme.Time.Core                      (UTCTime, posixSecondsToUTCTime)
import Data.Word                                 (Word32)
import HaskellWorks.Data.Network.Ip.Validity     (Canonical)

import qualified Data.Attoparsec.ByteString        as AP
import qualified Data.ByteString                   as BS
import qualified Data.ByteString.Lazy              as LBS
import qualified Data.ByteString.Lazy.Char8        as LC8
import qualified Data.Text.Encoding                as T
import qualified HaskellWorks.Data.Network.Ip.Ipv4 as IP4
import qualified HaskellWorks.Data.Network.Ip.Ipv6 as IP6

getMagic :: AP.Parser BS.ByteString -> Get ()
getMagic magicParser = do
  a <- getLazyByteString magicLength
  case AP.parseOnly magicParser (LBS.toStrict a) of
    Right _           -> return ()
    Left errorMessage -> fail $ "wrong magic: \"" <> LC8.unpack a <> "\", expected: " <> errorMessage

getSegmentLength :: Get Int
getSegmentLength = fromIntegral <$> getInt64le

getSegmentPosition :: Get (Int, Int)
getSegmentPosition = (,)
  <$> (fromIntegral <$> getInt32le)
  <*> (fromIntegral <$> getInt32le)

getSegmentPositions :: Get [(Int, Int)]
getSegmentPositions = do
  n <- getSegmentLength
  replicateM n getSegmentPosition

getHeader :: AP.Parser BS.ByteString -> Get [(Int, Int)]
getHeader magicParser = do
  getMagic magicParser
  getSegmentPositions

getTimeMicro64 :: Get POSIXTime
getTimeMicro64 = (^. from microseconds) <$> getInt64le

getTimeMillis :: Get UTCTime
getTimeMillis =
  let toTime ms = (ms * 1000) ^. from microseconds & posixSecondsToUTCTime
  in getInt64le <&> toTime

getTimeMicros :: Get UTCTime
getTimeMicros =
  let toTime ms = ms ^. from microseconds & posixSecondsToUTCTime
  in getInt64le <&> toTime

getWord32x4 :: Get (Word32, Word32, Word32, Word32)
getWord32x4 = do
  a <- getWord32be
  b <- getWord32be
  c <- getWord32be
  d <- getWord32be
  return (a, b, c, d)

getBool :: Get Bool
getBool = getWord8 <&> (/= 0)

getIpv4 :: Get IP4.IpAddress
getIpv4 = getWord32le <&> word32ToIpv4

getIpv6 :: Get IP6.IpAddress
getIpv6 = getWord32x4 <&> word32x4ToIpv6

getIpv4Block :: Get (IP4.IpBlock Canonical)
getIpv4Block =
  let toIpBlock w32 w8 = IP4.IpBlock (IP4.IpAddress w32) (IP4.IpNetMask w8)
  in toIpBlock <$> getWord32le <*> getWord8

getIpv6Block :: Get (IP6.IpBlock Canonical)
getIpv6Block =
  let toIpBlock w128 w8 = IP6.IpBlock (IP6.IpAddress w128) (IP6.IpNetMask w8)
  in toIpBlock <$> getWord32x4 <*> getWord8

getTextUtf8Z :: Get Text
getTextUtf8Z = T.decodeUtf8 . LBS.toStrict <$> getLazyByteStringNul

getNullTerminatedString :: Get LBS.ByteString
getNullTerminatedString = getLazyByteStringNul