--
-- Copyright © 2013-2014 Anchor Systems, Pty Ltd and Others
--
-- The code in this file, and the program it is a part of, is
-- made available to you by its authors as open source software:
-- you can redistribute it and/or modify it under the terms of
-- the 3-clause BSD licence.
--

{-# LANGUAGE OverloadedStrings #-}

module Vaultaire.Types.ReadRequest
(
    ReadRequest(..),
) where

import Control.Applicative
import Control.Exception
import Data.ByteString (ByteString)
import qualified Data.ByteString as S
import Data.Packer (getBytes, getWord64LE, getWord8, putBytes, putWord64LE,
                    putWord8, runPacking, tryUnpacking)
import Data.Word (Word8)
import Test.QuickCheck
import Text.Printf
import Vaultaire.Classes.WireFormat
import Vaultaire.Types.Address
import Vaultaire.Types.TimeStamp

data ReadRequest = SimpleReadRequest Address TimeStamp TimeStamp
                 | ExtendedReadRequest Address TimeStamp TimeStamp
  deriving (Eq)

-- For use in debugging output in the reader daemon. Could use TimeStamp's Show
-- instance, but that's in ISO8601, and we just want a Unix timestamp here.
instance Show ReadRequest where
    show (SimpleReadRequest   addr start end) = show addr ++ " (s) " ++ format start ++ " to " ++ format end
    show (ExtendedReadRequest addr start end) = show addr ++ " (e) " ++ format start ++ " to " ++ format end

format :: TimeStamp -> String
format (TimeStamp t) = printf "%010d" (t `div` 1000000000)

instance WireFormat ReadRequest where
    toWire (SimpleReadRequest addr start end) =
        packWithHeaderByte 0 addr start end
    toWire (ExtendedReadRequest addr start end) =
        packWithHeaderByte 1 addr start end

    fromWire bs
        | S.length bs /= 25 = Left . SomeException $ userError "expected 25 bytes"
        | otherwise = flip tryUnpacking bs $ do
            header <- getWord8
            addr_bytes <- getBytes 8
            addr <- either (fail . show) return $ fromWire addr_bytes
            start <- TimeStamp <$> getWord64LE
            end <- TimeStamp <$> getWord64LE
            case header of
                0 -> return $ SimpleReadRequest addr start end
                1 -> return $ ExtendedReadRequest addr start end
                _ -> fail "invalid header byte"

packWithHeaderByte :: Word8 -> Address -> TimeStamp -> TimeStamp -> ByteString
packWithHeaderByte header addr start end =
    let addr_bytes = toWire addr
    in runPacking 25 $ do
        putWord8 header
        putBytes addr_bytes
        putWord64LE (unTimeStamp start)
        putWord64LE (unTimeStamp end)

instance Arbitrary ReadRequest where
    arbitrary =
        oneof [ SimpleReadRequest <$> arbitrary <*> arbitrary <*> arbitrary
              , ExtendedReadRequest <$> arbitrary <*> arbitrary <*> arbitrary ]