{-# LANGUAGE ScopedTypeVariables, FlexibleContexts, Trustworthy #-}

{-
| Here I document the header layout. Every entity in a file has a header attached.
The header has enough information to traverse the graph structure of data.
It is laid out as follows:

* The first bit determines whether the entity is an algebraic type. If it is
zero, the next seven bits specify the index of the constructor represented,
minus one. The next bit is a parity check bit. The seven bits following
that specify how many parameters the constructor takes, and therefore the
number of four-byte addresses in the body of the entity.

* If the first bit is one, the second bit specifies whether the entity
is a primitive type (0) or an array (1). In this case the remaining six bits
in the first byte are reserved and must be zero, and the entity follows.

* The body of a primitive type is always four bytes long.

* The body of an array consists of a four-byte length entry, followed
by that many four-byte addresses.-}
module Data.Columbia.Internal.Headers where

import Data.Bits
import Data.Word
import Data.Int
import Data.Array
import Data.Maybe
import Data.Generics.SYB.WithClass.Basics
import Control.Monad.Reader
import Control.Monad.State
import Control.Monad.Trans
import Control.Monad.Identity
import Control.Monad
import Control.Parallel.Strategies
import Data.Columbia.Internal.SeekableStream
import Data.Columbia.Internal.SeekableWriter
import Data.Columbia.Internal.RWInstances
import Data.Columbia.Internal.IntegralTypes
import Data.Columbia.Types
import Data.Columbia.FRecord

evalTriple = evalTuple3 rseq rseq rseq

parity x1 fields = (popCount x1 + popCount fields) .&. 1

readHeader :: (Monad m) => ReaderT(SeekableStream m Word8) m (Word8, ConIndex, Int)
readHeader = do
	x1 <- consumeToken
	if testBit x1 7 then
			let x = (case x1 of
				160 -> 3
				192 -> 2
				128 -> 1
				_ -> error$"readHeader: header code "++show x1++" is invalid") in
			return$!using(x, 0, 0) evalTriple
		else do
		x2 <- consumeToken
		let fields = fromIntegral x2 .&. 127
		when(testBit x2 7 /= (parity x1 fields == 1))$
			fail"readHeader: parity check error"
		return$!using(0, fromIntegral x1, fields) evalTriple

{-# INLINE enhancedFromConstr #-}
enhancedFromConstr :: forall ctx a. (Data ctx a) => Proxy ctx -> DataType -> (Word8, ConIndex, Int) -> Int -> a
enhancedFromConstr proxy0 ty hdr@(_, ix, _) l = maybe
	(runIdentity$fromConstrM proxy0(return$error"enhancedFromConstr: constr is too strict")$indexConstr ty ix)
	runIdentity
	(dataCast1 proxy0(return$listArray(1,l)$repeat$error"enhancedFromConstr: uninitialized array"))

nConstructorParameters :: forall ctx d. (Data ctx d) => Proxy ctx -> d -> Int
nConstructorParameters proxy d =
	execState(gmapM
		proxy
		(\_ -> do { modify succ; return$error"nConstructorParameters: unused" })
		d)
		0

headerFromConstr :: forall ctx d. (Data ctx d) => Proxy ctx -> d -> Constr -> (Word8, ConIndex, Int)
headerFromConstr proxy d constr = hdr where
	ty = dataTypeOf proxy d
	specimen :: d = fromConstr proxy constr
	hdr = if dataTypeName ty == "Data.Array.Unboxed.UArray" then
			(3, 0, 0)
		else if dataTypeName ty == "Data.Array.Array" then
			(2, 0, 0)
		else if not(isAlgType ty) then
			(1, 0, 0)
		else
			(0, constrIndex constr, nConstructorParameters proxy specimen) where

writeHeader :: (Monad m, Data cxt d) => Proxy cxt -> d -> ReaderT(SeekableWriter m Word8) m ()
writeHeader proxy d = do
	let (n, conIndex, nFields) = headerFromConstr proxy d(toConstr proxy d)
	case n of
		3 -> putToken 160
		2 -> putToken 192
		1 -> putToken 128
		0 -> do
			when(conIndex>127)$fail"writeCompoundData: constructor index not in range 1-127"
			putToken(fromIntegral conIndex)
			when(nFields>127)$fail"writeCompoundData: number of fields not in range 0-127"
			putToken(fromIntegral$nFields .|. shiftL(parity conIndex nFields) 7)

nFieldsBytes hdr@(_, _, nFields) = if isHeaderPrimtype hdr then
		return(0, 5)
	else if isHeaderUArraytype hdr then do
		len <- readIntegral
		return(0, 5+len)
	else if isHeaderArraytype hdr then do
		len <- readIntegral
		return(len, 5+4*len)
	else
		return(nFields, 2+4*nFields)

isHeaderUArraytype :: (Word8, ConIndex, Int) -> Bool
isHeaderUArraytype (n, _, _) = n == 3

isHeaderArraytype :: (Word8, ConIndex, Int) -> Bool
isHeaderArraytype (n, _, _) = n == 2

isHeaderPrimtype :: (Word8, ConIndex, Int) -> Bool
isHeaderPrimtype (n, _, _) = n == 1

isHeaderAlgtype :: (Word8, ConIndex, Int) -> Bool
isHeaderAlgtype (n, _, _) = n == 0

-- | A public form of the 'readHeader' function that maintains the pre/postconditions.
readHeader' :: (Monad m) => ReaderT(SeekableStream m Word8) m Header
readHeader' = do
	n <- getPosition
	(x,x2,x3) <- readHeader
	seek n
	return$!Header x x2 x3