-- | A DSL for describing data laid out in a regular fashion with
-- a combination of structures and arrays.

module Data.Layout.Language (
    -- * Combinators
      repeat
    , offset
    , group

    -- * Value Formats
    , word8
    , word16le
    , word32le
    , word64le
    , word16be
    , word32be
    , word64be

    -- * Optimizations
    , optimize

    -- * Utility
    , size
    , byteOrder
    , valueSize1
    , valueSizeN
    , valueCount
    , chunk

    ) where

import Prelude hiding (repeat)

import Data.Layout.Internal

------------------------------------------------------------------------
-- Combinators

-- | Repeat a layout 'n' times.
repeat :: Reps -> Layout -> Layout
repeat n | n > 1     = Repeat n
         | n == 1    = id
         | otherwise = invalid "repeat" "repetition must be at least 1"

-- | Skip 'n' bytes before accessing the next part of the layout.
offset :: Bytes -> Layout -> Layout
offset n | n > 0     = Offset n
         | n == 0    = id
         | otherwise = invalid "offset" "must skip 0 or more bytes"

-- | Wrap a layout with a group which is 'n' bytes in size.
group :: Bytes -> Layout -> Layout
group n xs | n <= 0      = invalid "group" "must contain 1 or more bytes"
           | n < size xs = invalid "group" "cannot be smaller than the inner layout"
           | otherwise   = Group n xs

invalid :: String -> String -> a
invalid fn msg = error ("Data.Layout.Language." ++ fn ++ ": " ++ msg)


------------------------------------------------------------------------
-- Value Formats

-- | 8-bit word.
word8 :: Layout
word8 = Value Word8

-- | 16-bit word, little endian.
word16le :: Layout
word16le = Value Word16le

-- | 32-bit word, little endian.
word32le :: Layout
word32le = Value Word32le

-- | 64-bit word, little endian.
word64le :: Layout
word64le = Value Word64le

-- | 16-bit word, big endian.
word16be :: Layout
word16be = Value Word16be

-- | 32-bit word, big endian.
word32be :: Layout
word32be = Value Word32be

-- | 64-bit word, big endian.
word64be :: Layout
word64be = Value Word64be

formatSize :: ValueFormat -> Bytes
formatSize Word8    = 1
formatSize Word16le = 2
formatSize Word32le = 4
formatSize Word64le = 8
formatSize Word16be = 2
formatSize Word32be = 4
formatSize Word64be = 8

formatByteOrder :: ValueFormat -> ByteOrder
formatByteOrder Word8    = NoByteOrder
formatByteOrder Word16le = LittleEndian
formatByteOrder Word32le = LittleEndian
formatByteOrder Word64le = LittleEndian
formatByteOrder Word16be = BigEndian
formatByteOrder Word32be = BigEndian
formatByteOrder Word64be = BigEndian

------------------------------------------------------------------------
-- Optimizations

data GroupStatus = NoGroup | GotGroup

-- | Removes redundant data groups. Grouping a set of data is only
-- sensible when the data can be repeated. If we find multiple
-- groupings without a repeat in the middle we can remove all but
-- the outer group.
optimizeGroups :: Layout -> Layout
optimizeGroups = go NoGroup
  where
    -- found a group, pass GotGroup to inner layout
    go NoGroup  (Group n xs) = Group n (go GotGroup xs)

    -- already got an outer group, drop this one as it is redundant
    go GotGroup (Group _ xs) = go GotGroup xs

    -- reset group status, further grouping may be required for repeats
    go _       (Repeat n xs) = Repeat n (go NoGroup xs)

    -- other configurations are uninteresting for this optimization
    go status x              = mapInner (go status) x

-- | Folds adjancent data offset operations in to a single one.
optimizeOffsets :: Layout -> Layout
optimizeOffsets = go
  where
    go (Offset n (Offset m xs)) = go (Offset (n + m) xs)
    go x                        = mapInner go x

-- | Remove redundancies from a layout.
optimize :: Layout -> Layout
optimize = optimizeOffsets . optimizeGroups


------------------------------------------------------------------------
-- Utility

-- | Calculates the total size of the layout.
size :: Layout -> Int
size (Value  x)    = formatSize x
size (Offset x xs) = x + size xs
size (Group  x _)  = x
size (Repeat n xs) = n * size xs

-- | Gets the format of a single value in the layout.
valueFormat :: Layout -> ValueFormat
valueFormat (Value fmt)   = fmt
valueFormat (Offset _ xs) = valueFormat xs
valueFormat (Group  _ xs) = valueFormat xs
valueFormat (Repeat _ xs) = valueFormat xs

-- | Gets the byte order of the values in the layout.
byteOrder :: Layout -> ByteOrder
byteOrder = formatByteOrder . valueFormat

-- | Calculates the size of a single value in the layout.
valueSize1 :: Layout -> Int
valueSize1 = formatSize . valueFormat

-- | Calculates the total size of the values stored in the layout.
valueSizeN :: Layout -> Int
valueSizeN x = valueSize1 x * valueCount x

-- | Counts the number of times a value is repeated in the layout.
valueCount :: Layout -> Int
valueCount (Value  _)    = 1
valueCount (Offset _ xs) = valueCount xs
valueCount (Group  _ xs) = valueCount xs
valueCount (Repeat n xs) = valueCount xs * n

-- | Applys a transformation to the inner layout.
mapInner :: (Layout -> Layout) -> Layout -> Layout
mapInner _ (Value fmt)   = Value fmt
mapInner f (Offset n xs) = Offset n (f xs)
mapInner f (Group  n xs) = Group  n (f xs)
mapInner f (Repeat n xs) = Repeat n (f xs)

-- | Splits a repeated layout in to multiple layouts each with a maximum
-- of 'n' repetitions.
chunk :: Int -> Layout -> [Layout]
chunk n (Repeat m xs) = replicate (m `div` n) (Repeat n xs) ++ [Repeat (m `rem` n) xs]
chunk _ xs            = [xs]
