{-# LANGUAGE DeriveGeneric #-}
module Data.Dwarf.Reader where

import           Control.Applicative ((<$>), pure)
import           Data.Binary.Get (getWord16be, getWord32be, getWord64be, getWord16le, getWord32le, getWord64le, Get)
import qualified Data.Binary.Get as Get
import           Data.Word (Word16, Word32, Word64)
import           GHC.Generics (Generic)
import           TextShow (TextShow(..))
import           TextShow.Generic (genericShowbPrec)

data Endianess = LittleEndian | BigEndian
  deriving (Eq, Ord, Read, Show, Generic)

instance TextShow Endianess where showbPrec = genericShowbPrec

data Encoding = Encoding32 | Encoding64
  deriving (Eq, Ord, Read, Show, Generic)

instance TextShow Encoding where showbPrec = genericShowbPrec

data TargetSize = TargetSize32 | TargetSize64
  deriving (Eq, Ord, Read, Show, Generic)

instance TextShow TargetSize where showbPrec = genericShowbPrec

endianReader :: Endianess -> EndianReader
endianReader LittleEndian = EndianReader LittleEndian getWord16le getWord32le getWord64le
endianReader BigEndian    = EndianReader BigEndian    getWord16be getWord32be getWord64be
endianSizeReader :: Encoding -> EndianReader -> EndianSizeReader
endianSizeReader Encoding64 der = EndianSizeReader der Encoding64 0xffffffffffffffff (derGetW64 der)
endianSizeReader Encoding32 der = EndianSizeReader der Encoding32 0xffffffff (fromIntegral <$> derGetW32 der)
reader :: TargetSize -> EndianSizeReader -> Reader
reader TargetSize64 desr = Reader desr TargetSize64 0xffffffffffffffff (desrGetW64 desr)
reader TargetSize32 desr = Reader desr TargetSize32 0xffffffff         $ fromIntegral <$> desrGetW32 desr

desrGetW16 :: EndianSizeReader -> Get Word16
desrGetW16 = derGetW16 . desrEndianReader
desrGetW32 :: EndianSizeReader -> Get Word32
desrGetW32 = derGetW32 . desrEndianReader
desrGetW64 :: EndianSizeReader -> Get Word64
desrGetW64 = derGetW64 . desrEndianReader
drGetW16 :: Reader -> Get Word16
drGetW16 = desrGetW16 . drDesr
drGetW32 :: Reader -> Get Word32
drGetW32 = desrGetW32 . drDesr
drGetW64 :: Reader -> Get Word64
drGetW64 = desrGetW64 . drDesr
drGetOffset :: Reader -> Get Word64
drGetOffset = desrGetOffset . drDesr
drLargestOffset :: Reader -> Word64
drLargestOffset = desrLargestOffset . drDesr

-- Intermediate data structure for a partial Reader.
data EndianReader = EndianReader
  { derEndianess :: Endianess
  , derGetW16 :: Get Word16
  , derGetW32 :: Get Word32
  , derGetW64 :: Get Word64
  }

-- Intermediate data structure for a partial Reader.
data EndianSizeReader = EndianSizeReader
  { desrEndianReader :: EndianReader
  , desrEncoding :: Encoding
  , desrLargestOffset :: Word64
  , desrGetOffset :: Get Word64
  }

-- | Type containing functions and data needed for decoding DWARF information.
data Reader = Reader
    { drDesr                  :: EndianSizeReader
    , drTarget64              :: TargetSize
    , drLargestTargetAddress  :: Word64     -- ^ Largest permissible target address.
    , drGetTargetAddress :: Get Word64 -- ^ Action for reading a pointer for the target machine.
    }

-- Decode the DWARF size header entry, which specifies both the size of a DWARF subsection and whether this section uses DWARF32 or DWARF64.
getUnitLength :: EndianReader -> Get (EndianSizeReader, Word64)
getUnitLength der = do
    size <- derGetW32 der
    if size == 0xffffffff then do
        size64 <- derGetW64 der
        pos <- Get.bytesRead
        pure (endianSizeReader Encoding64 der, fromIntegral pos + size64)
     else
      if size < 0xffffff00 then do
        pos <- Get.bytesRead
        pure (endianSizeReader Encoding32 der, fromIntegral pos + fromIntegral size)
      else
        fail $ "Invalid DWARF size: " ++ show size