{-# LANGUAGE DataKinds           #-}
{-# LANGUAGE DeriveGeneric       #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications    #-}

module Arbor.File.Format.Asif.Format.SegmentValue
where

import qualified Arbor.File.Format.Asif.ByteString.Lazy as LBS
import           Arbor.File.Format.Asif.Data.Ip
import qualified Arbor.File.Format.Asif.Format.Type     as F
import           Arbor.File.Format.Asif.List            as L
import           Arbor.File.Format.Asif.Segment
import           Arbor.File.Format.Asif.Whatever
import           Control.Lens
import qualified Data.Binary                            as G
import qualified Data.Binary.Get                        as G
import qualified Data.ByteString.Lazy                   as LBS
import qualified Data.ByteString.Lazy.Char8             as LBSC
import           Data.Generics.Product.Any
import           Data.Int
import           Data.IP
import           Data.Semigroup                         ((<>))
import qualified Data.Text                              as T
import           Data.Thyme.Time.Core
import           Data.Word
import           GHC.Generics                           (Generic)

data SegmentValue
  = SString LBS.ByteString
  | SBool Bool
  | SChar Char
  | STime UTCTime
  | SIpv4 IPv4
  | SIpv6 IPv6
  | SInt64 Int64
  | SInt32 Int32
  | SInt16 Int16
  | SInt8 Int8
  | SWord64 Word64
  | SWord32 Word32
  | SWord16 Word16
  | SWord8 Word8
  | SText LBS.ByteString
  | SBitString LBS.ByteString
  | SBitmap LBS.ByteString
  | SBinary LBS.ByteString
  | SList [SegmentValue]
  | SUnknown T.Text LBS.ByteString
  deriving (Show, Eq, Generic)

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

getValues :: Int64 -> G.Get a -> LBSC.ByteString -> [a]
getValues n f bs =
  let getValue bs' = G.runGet f (LBS.take n (bs' <> LBS.replicate n 0))
  in LBS.chunkBy n bs <&> getValue

getRawValue :: F.Format -> LBS.ByteString -> [SegmentValue]
getRawValue format bs =
  case format of
    F.StringZ ->
      if LBS.null bs
        then []
        else init (LBS.split 0 bs) <&> SString

    F.Bool -> whenNonEmpty bs $
      let toBool = (/=) 0
      in bs & getValues 1 G.getWord8 <&> (SBool . toBool)

    F.Char -> whenNonEmpty bs $
      LBSC.unpack bs <&> SChar

    F.TimeMillis64LE -> whenNonEmpty bs $
      let toTime ms = (ms * 1000) ^. from microseconds & posixSecondsToUTCTime
      in bs & getValues 8 G.getInt64le <&> (STime . toTime)

    F.TimeMicros64LE -> whenNonEmpty bs $
      let toTime ms = ms ^. from microseconds & posixSecondsToUTCTime
      in bs & getValues 8 G.getInt64le <&> (STime . toTime)

    F.Ipv4 -> whenNonEmpty bs $
      bs & getValues 4 G.getWord32le <&> (SIpv4 . word32ToIpv4)

    F.Ipv6 -> whenNonEmpty bs $
      bs & getValues 16 getWord32x4 <&> (SIpv6 . word32x4ToIpv6)

    F.Int64LE -> whenNonEmpty bs $
      bs & getValues 8 G.getInt64le <&> SInt64

    F.Int32LE -> whenNonEmpty bs $
      bs & getValues 4 G.getInt32le <&> SInt32

    F.Int16LE -> whenNonEmpty bs $
      bs & getValues 2 G.getInt16le <&> SInt16

    F.Int8 -> whenNonEmpty bs $
      bs & getValues 1 G.getInt8 <&> SInt8

    F.Word64LE -> whenNonEmpty bs $
      bs & getValues 8 G.getWord64le <&> SWord64

    F.Word32LE -> whenNonEmpty bs $
      bs & getValues 4 G.getWord32le <&> SWord32

    F.Word16LE -> whenNonEmpty bs $
      bs & getValues 2 G.getWord16le <&> SWord16

    F.Word8 -> whenNonEmpty bs $
      bs & getValues 1 G.getWord8 <&> SWord8

    F.Text ->
      [SText bs]

    F.Repeat n fmt@F.Text -> whenNonEmpty bs $
      LBS.chunkBy (fromIntegral n) bs >>= getRawValue fmt

    F.BitString ->
      [SBitString bs]

    F.Repeat n fmt@F.BitString -> whenNonEmpty bs $
      LBS.chunkBy (fromIntegral n) bs >>= getRawValue fmt

    F.Binary ->
      [SBinary bs]

    F.Repeat n fmt@F.Binary -> whenNonEmpty bs $
      LBS.chunkBy (fromIntegral n) bs >>= getRawValue fmt

    F.Bitmap ->
      [SBitmap bs]

    F.Repeat n fmt@F.Bitmap -> whenNonEmpty bs $
      LBS.chunkBy (fromIntegral n) bs >>= getRawValue fmt

    F.Repeat n fmt -> whenNonEmpty bs $
      getRawValue fmt bs & L.chunksOf (fromIntegral n) <&> SList

whenNonEmpty :: LBSC.ByteString -> [a] -> [a]
whenNonEmpty bs f =
  if LBS.null bs then [] else f

segmentValues :: Segment LBS.ByteString -> [SegmentValue]
segmentValues segment =
  case segment ^. the @"meta" . the @"format" of
    Just (Known format) -> getRawValue format (segment ^. the @"payload")
    Just (Unknown txt)  -> [SUnknown txt (segment ^. the @"payload")]
    Nothing             -> []